using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.IO;
using System.Xml;
using Microsoft.DirectX.DirectInput;
using System.Globalization;
using System.Text.RegularExpressions;

//-------------------------------------------------------------------------------- 
// Mini 3D Terrain Viewer
// Version 1.5 - March 2007
// http://www.alpix.com/3d/TerrainViewer/
// Patrick Murris <patmurris@videotron.ca>
// Licence : Public domain
//--------------------------------------------------------------------------------
// 1.0	June 14, 2006	First release
// 1.1	June 14, 2006	Corrected bug with decimal separator on french systems
// 1.2	June 15, 2006	Auto-generates texture from DEM if no texture specified 
//						or a color scale is refered from \colors
// 1.3	June 16, 2006	Enlarged tile list size and far clipping distance 
//						Added lighting option with key L /Shift L
//						Added support for SRTM .bil elevation files
//						Added drag-drop support for .png and .bil elevation files
//						Added some try/catch logic in LoadMap() and OnPaint()
// 1.4	Febr 23, 2007	Added orthographic projection
//						Added map sides
//						Added water plane
//						Moved all units to the meter (real) scale
//						Full 16bit support for .bil elevation files
//						Will infere .bil coord from file path if possible
//						Fixed tiles sides normals for shading
//						Light position handling with mouse drag (both buttons)
//						Added Load map menu in File
//						Added View, Textures, Sides and Sky menus
//						Command line parameters support for map list xml file
//						Added 'real' shadows - crude and slow (Key Ctrl L on textured maps)
// 1.5	March  2, 2007	Added floating section profile
//						Fixed shadow texture filtering
//						Added crosshair
//						Added world map
//						Added picking at terrain code
//						Added position info display
//--------------------------------------------------------------------------------
// Changes to the code by Jiri Kadlec
//--------------------------------------------------------------------------------
// 1.6 August 30, 2009 Changed the code to work as a MapWindow GIS plugin
//--------------------------------------------------------------------------------

namespace TerrainViewer
{ 

	public class Viewer : System.Windows.Forms.Form
	{
		public string appName = "MapWindow 3D Viewer";
		public string appVersion = "";

		private Microsoft.DirectX.Direct3D.Device device;
        private Microsoft.DirectX.DirectInput.Device keyb;
        private IContainer components;
		private System.Windows.Forms.MainMenu mainMenu;
		private System.Windows.Forms.MenuItem menuItemFile;
		private System.Windows.Forms.MenuItem menuItemLoadMaps;
		private System.Windows.Forms.MenuItem menuItemQuit;
		private System.Windows.Forms.MenuItem menuItemView;
		private System.Windows.Forms.MenuItem menuItemShowLight;
		private System.Windows.Forms.MenuItem menuItemShowWater;
		private System.Windows.Forms.MenuItem menuItemShowFog;
		private System.Windows.Forms.MenuItem menuItemShowSpin;
		private System.Windows.Forms.MenuItem menuItemVerticalFactor;
		private System.Windows.Forms.MenuItem menuItemMaps;
		private System.Windows.Forms.MenuItem menuItemTextures;
		private System.Windows.Forms.MenuItem menuItemSides;
		private System.Windows.Forms.MenuItem menuItemSky;
		private System.Windows.Forms.MenuItem menuItemLightMap;
		private System.Windows.Forms.MenuItem menuItemShowSection;
		private System.Windows.Forms.MenuItem menuItemShowInfo;
		private System.Windows.Forms.MenuItem menuItemShowCrosshair;
		private System.Windows.Forms.MenuItem menuItemShowWorldMap;

		private double worldRadius = 6378000;			// World radius (meters)
		private double mapSpan;							// Angular span of terrain (decimal degrees)
		private double mapWidth;						// Distance span of terrain (meters)
		private double mapLat = 0;						// Latitude of map center (decimal degrees)
		private double mapLon = 0;						// Longitude of map center (decimal degrees)
		private double mapHeading = 0;					// Direction of map top from North CCW (decimal degrees)
		private double mapMaxAlt = 9000;				// Maximum altitude (meters)
		private double mapMinAlt = 0;					// Minimum altitude (meters)
		private bool dem16 = false;						// True if dem data 16 bits (.bil)

		private bool redraw = true;						// Redraw scene when true
		private float angle =  (float)Math.PI / 2;		// Spin angle on Z (map rotation)
		private float angle2 = (float)Math.PI / 4;		// Spin angle on Y (map inclinaison)
		private float dx = 0;							// Map displacement X
		private float dy = 0;							// Map displacement Y
		private float dz = 0;							// Map displacement Z (height)
		private float dist = 170;						// Camera distance from target on map
        private float initialDist = 170;                // Distance on initial view
		float fov = (float)Math.PI/4;					// Camera field of view - radian
		private bool spin = false;						// Auto rotate map if true
		private Mesh skyMesh;							// Sky dome mesh
		private Mesh sectionMesh;						// Cross section mesh
		private Mesh waterMesh;							// Water mesh
		private Mesh sidesMesh;							// Terrain sides mesh
		private Mesh[] meshList;						// List of tiles
		private int meshCount = 0;						// Number of tiles
		private int meshWidth = 64;						// Number of rows/cols per tile
		private Texture texture;						// Current terrain texture
		private Texture skyTexture;						// Current sky texture
		private Texture sidesTexture;					// Current sides texture
		private Texture lightMapTexture;				// Current light map texture
		public string mapName = "";						// Name of current map
		public string mapListFileName = "maps.xml";		// Map list xml file (default)
		public string mapListPath = "";					// Rooted path to map list xml file
		public string textureFileName;					// Current terrain texture file
		public string terrainFileName;					// Current DEM file
        public Bitmap textureImage;                     // The texture image (stored in resource file)
        public Bitmap sidesImage;                       // The 'sides' image (stored in resource file)
		Bitmap DEM;										// DEM data
		public string skyFileName = "";					// Current sky file
		public string sidesFileName = "";				// Current sides texture file
		public float verticalFactor = 0.1f;				// Vertical exaggeration/correction
        public float verticalFactor2 = 5.0f;            // User-defined increase or decrease of vertical factor
        public float defaultVerticalFactor = 0.1f;      // Default vertical factor of the supplied DEM
        bool showSection = false;						// Show cross section when true
		bool showFog = false;							// Use fog effect when true
		bool showWater = true;							// Show water when true
		bool showInfo = true;							// Show info text when true
		bool showCrosshair = false;						// Show crosshair when true
		Color fogColor = Color.FromArgb(208, 208, 208);	// Fog color
		Color waterColor = Color.FromArgb(128, 0, 40, 128);	// Water color
		Color ambientColor = Color.FromArgb(60, 60, 60);	// Ambient color
		Material material;								// Default material
		bool showLight = true;							// Use light when true
		bool fixedLight = true;						// Light has fixed position from map when true
		private float lightHeading =  (float)(-Math.PI / 4);		// Light source direction
		private float lightElevation = (float)(Math.PI / 4);	// Light source elevation above horizon
		int rightClickFactor = -1;						// Which way the map spin on right-click drag (1 or -1)
		bool showTransparentTerrain = false;			// Apply some transparency to terrain when true
		int terrainOpacity = 32;						// Opacity when terrain is transparent
		Sprite sprite;									// Default sprite for text
		string infoText = "";							// Info text
		Microsoft.DirectX.Direct3D.Font font;			// Default font
		bool showWorldMap = false;						// Display global map when true

        string lakeFolder = @"lakes\Lake Mendota";      // location where to search for the lake file

		// Mouse tracking
		private Point mouse = Point.Empty;
		private Point mouseDownStartPosition = Point.Empty;
		private float mouseDownStartDx;
		private float mouseDownStartDy;
		private float mouseDownStartAngle;
		private float mouseDownStartAngle2;
		private float mouseDownLightAngle;
        private Panel panel1;
        private System.Windows.Forms.Button btnRight;
        private System.Windows.Forms.Button btnLeft;
        private System.Windows.Forms.Button btnDown;
        private System.Windows.Forms.Button btnUp;
        private System.Windows.Forms.Button btnZoomOut;
        private System.Windows.Forms.Button btnZoomIn;
		private float mouseDownLightAngle2;

        // Navigation button status
        private Navigation navigationType = Navigation.None;
        private Label lblOverviewMap;
        private Label lblVerticalFactor;
        private NoWheelComboBox cmbVerticalFactor;
        private Timer navigationTimer;

        // overview map
        private Image arrowImage = null;
        private System.Windows.Forms.Button btnReset;
        private System.Windows.Forms.Button btnReset2;
        private ToolTip toolTip1;
        private Label lblNorth;
        private Label lblW;
        private Label lblE;
        private Label label1;
        private Image backgroundImage = null;

        [STAThread()]
		static void Main(string[] args)
		{
			using (Viewer viewer = new Viewer(args))
			{
                Application.EnableVisualStyles();
                
                viewer.Show();
				viewer.OnPaint(null);
				try 
				{
					viewer.MapMenuSelectMap(0);		// Load first map
				}
				catch {}
				Application.Run(viewer);		// Run
			}
		}

		public Viewer(string[] args)
		{
			InitializeComponent();
			this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque | ControlStyles.UserPaint, true);
			this.meshList = new Mesh[512];			// Max number of tiles

			// Drag drop
            //this.AllowDrop = true;
            //this.DragEnter += new DragEventHandler(this.OnDragEnter);
            //this.DragDrop += new DragEventHandler(this.OnDragDrop);

			// Init
			this.ProcessArgs(args);				// Read command line args
			this.InitializeDevice();			// Direct 3D device setup
			this.InitializeKeyboard();			// Keyboard setup
			this.InitializeVerticalFactorMenu(); // Create vertical factor menu
			this.InitializeTexturesMenu();		// Create Textures menu
			this.InitializeSidesMenu();			// Create Sides menu
			this.InitializeSkyMenu();			// Create Sky menu

            // Init added by Jiri Kadlec
            this.InitializeNavigationButtons(); // Create the navigation buttons
            this.LoadOverview(lakeFolder);

            //commented out by JK
			//this.worldMap = new WorldMap(device, "Earth_Map.png");
			this.font = CreateFont("Tahoma", 8f, FontStyle.Regular);
			this.sprite = new Sprite(device);

			try 
			{
				this.InitializeMapList();			// Create Map menu
			}
			catch 
			{
				MenuEnabled(false);
			}
		}
 
		public void ProcessArgs(string[] args)
		{
			string allArgs = "";
            for (int i = 0; i < args.Length; i++)
			{
				//if(args[i].EndsWith(".xml")) mapListFileName = args[i];

                if (i == 0)
                {
                    allArgs += args[i];
                }
                else
                {
                    allArgs = allArgs + " " + args[i];
                }
			}
            mapListFileName = allArgs;
            if (File.Exists(mapListFileName))
            {
                lakeFolder = Path.GetDirectoryName(mapListFileName);
            }
		}

		public void InitializeDevice()
		{
			// Create device
			PresentParameters presentParams = new PresentParameters();
			presentParams.Windowed = true;
			presentParams.SwapEffect = SwapEffect.Discard;
			presentParams.AutoDepthStencilFormat = DepthFormat.D16;
			presentParams.EnableAutoDepthStencil = true;
			presentParams.PresentationInterval = PresentInterval.Immediate;

            //device = new Microsoft.DirectX.Direct3D.Device(0, Microsoft.DirectX.Direct3D.DeviceType.Hardware, panel1, CreateFlags.SoftwareVertexProcessing, presentParams);
            device = new Microsoft.DirectX.Direct3D.Device(0, Microsoft.DirectX.Direct3D.DeviceType.Hardware, this, CreateFlags.SoftwareVertexProcessing, presentParams);
			// Events
			device.DeviceReset += new EventHandler(this.OnDeviceReset);
			device.DeviceResizing += new CancelEventHandler(OnDeviceResizing);
			// Set up
			OnDeviceReset(device, null);
		}

		protected void OnDeviceReset(object sender, EventArgs e)
		{
			// Rendering setup
			device.RenderState.FillMode = FillMode.Solid;
			device.RenderState.CullMode = Cull.CounterClockwise;
			device.RenderState.SourceBlend = Blend.SourceAlpha;
			device.RenderState.DestinationBlend = Blend.InvSourceAlpha;
			device.RenderState.AlphaBlendEnable = true;
			device.RenderState.ZBufferEnable = true;
			// Texture filtering
			device.SamplerState[0].MipFilter = TextureFilter.Linear;
			if( device.DeviceCaps.TextureFilterCaps.SupportsMinifyAnisotropic)
			{
				device.SamplerState[0].MinFilter = TextureFilter.Anisotropic;
				device.SamplerState[1].MinFilter = TextureFilter.Anisotropic;
			}
			else if( device.DeviceCaps.TextureFilterCaps.SupportsMinifyLinear)
			{
				device.SamplerState[0].MinFilter = TextureFilter.Linear;
				device.SamplerState[1].MinFilter = TextureFilter.Linear;
			}
			if( device.DeviceCaps.TextureFilterCaps.SupportsMagnifyAnisotropic )
			{
				device.SamplerState[0].MagFilter = TextureFilter.Anisotropic;
				device.SamplerState[1].MagFilter = TextureFilter.Anisotropic;
			}
			else if( device.DeviceCaps.TextureFilterCaps.SupportsMagnifyLinear )
			{
				device.SamplerState[0].MagFilter = TextureFilter.Linear;
				device.SamplerState[1].MagFilter = TextureFilter.Linear;
			}
			device.SamplerState[0].AddressU = TextureAddress.Clamp;
			device.SamplerState[0].AddressV = TextureAddress.Clamp;
			device.SamplerState[1].AddressU = TextureAddress.Clamp;
			device.SamplerState[1].AddressV = TextureAddress.Clamp;
			// Texture stage states.
			device.TextureState[0].ColorOperation = TextureOperation.SelectArg1;
			device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
			device.TextureState[0].AlphaOperation = TextureOperation.SelectArg1;
			device.TextureState[0].AlphaArgument1 = TextureArgument.TextureColor;
			redraw = true;
		}
 
		private void OnDeviceResizing(object sender, CancelEventArgs e)
		{
			if(this.Size.Width == 0 || this.Size.Height == 0)
			{
				e.Cancel = true;
				return;
			}
			redraw = true;
		}

		protected void OnFormMove(object sender, EventArgs e)
		{
			redraw = true;
		}

		// Load map list into the Map menu
		public void InitializeMapList()
		{
			// Clear Maps menu if not empty
			while(menuItemMaps.MenuItems.Count > 0) menuItemMaps.MenuItems.RemoveAt(0);

			// Set mapListPath
			string fn = Path.GetFileName(mapListFileName);
			mapListPath = Path.GetFullPath(mapListFileName).Replace(fn, "");			

			XmlTextReader reader = null;
			// Load the reader with the data file and ignore all white space nodes.         
			reader = new XmlTextReader(mapListFileName);
			reader.WhitespaceHandling = WhitespaceHandling.None;

			// Parse the file.
			while (reader.Read()) 
			{
				switch (reader.NodeType) 
				{
					case XmlNodeType.Element:
					switch(reader.Name)
					{
						case "map" :
							if(reader["name"] != null)	// Add to Maps menu
							{
								MenuItem mi = new MenuItem(reader["name"]);
								mi.Click += new System.EventHandler(this.menuItemMaps_Click);
								menuItemMaps.MenuItems.Add(mi);			
							}
							break;
					}
						break;
				}       
			}  
			reader.Close();
		}

		// Initialize vertical factor menu
		public void InitializeVerticalFactorMenu()
		{
			// First one is current
			MenuItem mi = new MenuItem("(current)");
			mi.Click += new System.EventHandler(this.menuItemVerticalFactor_Click);
			menuItemVerticalFactor.MenuItems.Add(mi);
			mi = new MenuItem("-");
			menuItemVerticalFactor.MenuItems.Add(mi);
			for(int i = 0; i <= 15; i++)
			{
				mi = new MenuItem("x" + i.ToString());
				mi.Click += new System.EventHandler(this.menuItemVerticalFactor_Click);
				menuItemVerticalFactor.MenuItems.Add(mi);			
			}

            // initialize the combo box items
            cmbVerticalFactor.SuspendLayout();
            cmbVerticalFactor.Items.Clear();
            cmbVerticalFactor.Items.Add("1");
            cmbVerticalFactor.Items.Add("2");
            cmbVerticalFactor.Items.Add("5");
            cmbVerticalFactor.Items.Add("10");
            cmbVerticalFactor.Items.Add("20");
            cmbVerticalFactor.SelectedIndex = 2; //default vertical factor is '2'
            cmbVerticalFactor.ResumeLayout();
            //cmbVerticalFactor.Enabled = false; //temporarily disable the change
		}

        // Load textures/colors list into the Textures menu
        public void InitializeTexturesMenu()
		{
			// First one is default map texture
			MenuItem mi = new MenuItem("(default)");
			mi.Click += new System.EventHandler(this.menuItemTextures_Click);
			menuItemTextures.MenuItems.Add(mi);
			mi = new MenuItem("-");
			menuItemTextures.MenuItems.Add(mi);
			// Read colors available
			string dir = "colors/";
            DirectoryInfo di = new DirectoryInfo(dir);
            if (di.Exists)
            {
                FileInfo[] imgFiles = di.GetFiles("*.png");
                for (int i = 0; i < imgFiles.Length; i++)	// Add to menu
                {
                    mi = new MenuItem(dir + imgFiles[i]);
                    mi.Click += new System.EventHandler(this.menuItemTextures_Click);
                    menuItemTextures.MenuItems.Add(mi);
                }
            }
            else
            {
                //try to read colors from Images.resx resource file
                
            }
		}

		// Change terrain texture from Textures menu
		private void menuItemTextures_Click(object sender, System.EventArgs e)
		{
			if(textureFileName == null) return;
			// Free current resources
			if(texture != null) texture.Dispose();
			texture = null;
			MenuClearCheck(menuItemTextures);
			// Check if we need to rebuild the mesh
			MenuItem mi = (MenuItem)sender;
			bool rebuild = false;
			if(textureFileName.IndexOf("colors") == -1 &&  mi.Text.IndexOf("colors") != -1) rebuild = true;
			if(textureFileName.IndexOf("colors") != -1 &&  mi.Text.IndexOf("colors") == -1) rebuild = true;
			if(textureFileName.IndexOf("Water") == -1 &&  mi.Text.IndexOf("Water") != -1) rebuild = true;
			if(textureFileName.IndexOf("Water") != -1 &&  mi.Text.IndexOf("Water") == -1) rebuild = true;
			// Switch texture
			if(mi.Text != "")
			{
				this.Cursor = Cursors.WaitCursor;
				try 
				{
					textureFileName = mi.Text;
					texture = TextureLoader.FromFile(device, GetFullPathTo(textureFileName));
					mi.Checked = true;
					if(rebuild) 
					{
						DisposeTerrainMesh();
						BuildTerrainMesh();
					}
				} 
				catch {texture = null;} 
				this.Cursor = Cursors.Default;
				if(textureFileName.IndexOf("colors") != -1) menuItemLightMap.Enabled = false;
				else  menuItemLightMap.Enabled = true;
			}
			redraw = true;
		}


		// Load sides/colors list into the Sides menu
		public void InitializeSidesMenu()
		{
			// First one is default sides texture
			MenuItem mi = new MenuItem("(default)");
			mi.Click += new System.EventHandler(this.menuItemSides_Click);
			menuItemSides.MenuItems.Add(mi);
			mi = new MenuItem("-");
			menuItemSides.MenuItems.Add(mi);
			// Read colors available
			string dir = "colors/";
            DirectoryInfo di = new DirectoryInfo(dir);
            if (di.Exists)
            {
                FileInfo[] imgFiles = di.GetFiles("*.png");
                for (int i = 0; i < imgFiles.Length; i++)	// Add to menu
                {
                    mi = new MenuItem(dir + imgFiles[i]);
                    mi.Click += new System.EventHandler(this.menuItemSides_Click);
                    menuItemSides.MenuItems.Add(mi);
                }
            }
		}

		// Change terrain texture from Textures menu
		private void menuItemSides_Click(object sender, System.EventArgs e)
		{
			if(textureFileName == null) return;
			// Free current resources
			if(sidesTexture != null) sidesTexture.Dispose();
			sidesTexture = null;
			MenuClearCheck(menuItemSides);
			// Check if we need to rebuild the mesh
			MenuItem mi = (MenuItem)sender;
			bool rebuild = false;
			if(sidesFileName.IndexOf("Water") == -1 &&  mi.Text.IndexOf("Water") != -1) rebuild = true;
			if(sidesFileName.IndexOf("Water") != -1 &&  mi.Text.IndexOf("Water") == -1) rebuild = true;
			// Switch texture
			if(mi.Text != "")
			{
				this.Cursor = Cursors.WaitCursor;
				try 
				{
					sidesFileName = mi.Text;
					sidesTexture = TextureLoader.FromFile(device, GetFullPathTo(sidesFileName));
					mi.Checked = true;
				} 
				catch {sidesTexture = null;} 
				if(rebuild) 
				{
					if(sidesMesh != null) sidesMesh.Dispose();
					sidesMesh = TerrainSides(device, DEM);
				}
				this.Cursor = Cursors.Default;
			}
			redraw = true;
		}

		// Load sky bitmaps list into the Sky menu
		public void InitializeSkyMenu()
		{
			// First one is default texture
			MenuItem mi = new MenuItem("(default)");
			mi.Click += new System.EventHandler(this.menuItemSky_Click);
			menuItemSky.MenuItems.Add(mi);
			mi = new MenuItem("-");
			menuItemSky.MenuItems.Add(mi);
			// Read images available
			string dir = "skys/";
            DirectoryInfo di = new DirectoryInfo(dir);
            if (di.Exists)
            {
                FileInfo[] imgFiles = di.GetFiles("*.jpg");
                for (int i = 0; i < imgFiles.Length; i++)	// Add jpg to menu
                {
                    mi = new MenuItem(dir + imgFiles[i]);
                    mi.Click += new System.EventHandler(this.menuItemSky_Click);
                    menuItemSky.MenuItems.Add(mi);
                }
                imgFiles = di.GetFiles("*.png");
                for (int i = 0; i < imgFiles.Length; i++)	// Add png to menu
                {
                    mi = new MenuItem(dir + imgFiles[i]);
                    mi.Click += new System.EventHandler(this.menuItemSky_Click);
                    menuItemSky.MenuItems.Add(mi);
                }
            }
		}

		// Load new sky bitmap
		private void menuItemSky_Click(object sender, System.EventArgs e)
		{
			if(textureFileName == null) return;
			if(skyTexture != null) skyTexture.Dispose();
			skyTexture = null;
			MenuClearCheck(menuItemSky);
			MenuItem mi = (MenuItem)sender;
			// Switch texture
			if(mi.Text != "")
			{
				this.Cursor = Cursors.WaitCursor;
				try 
				{
					skyFileName = mi.Text;
					skyTexture = TextureLoader.FromFile(device, GetFullPathTo(skyFileName));
					mi.Checked = true;
				} 
				catch {skyTexture = null;}
				this.Cursor = Cursors.Default;
			}
			redraw = true;
		}

		// Load map from Map menu
		private void menuItemMaps_Click(object sender, System.EventArgs e)
		{
			// Free current resources
			DisposeMap();
			// Switch maps here
			MenuItem mapItem = (MenuItem)sender;
			string name = mapItem.Text;
			// Load data from map list xml
			LoadMapData(name);
			// Update Maps menus
			MenuClearCheck(menuItemMaps);
			mapItem.Checked = true;
			// Load map resources
			LoadMap();
			redraw = true;
		}


		// Clear all check marks in a menu
		public void MenuClearCheck(MenuItem menu)
		{
			foreach(MenuItem mi in menu.MenuItems) mi.Checked = false;
		}

		public void MenuEnabled(bool state)
		{
			menuItemMaps.Enabled = state;
			menuItemView.Enabled = state;
			menuItemTextures.Enabled = state;
			menuItemSides.Enabled = state;
			menuItemSky.Enabled = state;
		}

		// Load a map from the Maps menu by its rank
		public void MapMenuSelectMap(int num)
		{
			foreach(MenuItem mi in menuItemMaps.MenuItems) 
			{
				if(mi.Index == num) { menuItemMaps_Click(mi, null); break; }
			}
		}

		// Load a map from the Maps menu by its name
		public void MapMenuSelectMap(string name)
		{
			foreach(MenuItem mi in menuItemMaps.MenuItems) 
			{
				if(mi.Text == name) { menuItemMaps_Click(mi, null); break; }
			}
		}

		// Load map data from the current map list xml file
		public void LoadMapData(string name) 
		{
			// Reset current map data
			textureFileName = "";
			terrainFileName = "";
			sidesFileName = "";
			skyFileName = "";
			mapLon = 0;
			mapLat = 0;
			mapSpan = 0;
			mapWidth = 0;
			mapHeading = 0;
			dem16 = false;
			verticalFactor = 1.0f;

			XmlTextReader reader = null;
			// Load the reader with the data file and ignore all white space nodes.         
			reader = new XmlTextReader(mapListFileName);
			reader.WhitespaceHandling = WhitespaceHandling.None;

			bool mapFound = false;
			// Parse the file.
			while (reader.Read()) 
			{
				switch (reader.NodeType) 
				{
					case XmlNodeType.Element:
					switch(reader.Name)
					{
						case "map" :
							if(reader["name"] != null)
							{
								if(reader["name"] == name) // Map found: switching
								{
									mapName = name;
									mapFound = true;
								}
								else
								{
									mapFound = false;
								}
							
							}
							break;
						case "terrain" :
							if(mapFound) 
							{
								if(reader["demfile"] != null) terrainFileName = reader["demfile"];
								if(reader["texturefile"] != null) textureFileName = reader["texturefile"];
								if(reader["sidesfile"] != null) sidesFileName = reader["sidesfile"];
								if(reader["centerlatitude"] != null) mapLat = double.Parse(reader["centerlatitude"], CultureInfo.InvariantCulture);
								if(reader["centerlongitude"] != null) mapLon = double.Parse(reader["centerlongitude"], CultureInfo.InvariantCulture);
								if(reader["span"] != null) mapSpan = double.Parse(reader["span"], CultureInfo.InvariantCulture);
								if(reader["heading"] != null)  mapHeading = double.Parse(reader["heading"], CultureInfo.InvariantCulture);
                                if (reader["altscale"] != null)
                                {
                                    defaultVerticalFactor = float.Parse(reader["altscale"], CultureInfo.InvariantCulture);
                                    verticalFactor = defaultVerticalFactor * verticalFactor2;
                                }
                                    if(reader["worldradius"] != null) worldRadius = double.Parse(reader["worldradius"], CultureInfo.InvariantCulture);
							}
							break;
						case "sky" :
							if(mapFound) 
							{
								if(reader["skyfile"] != null) skyFileName = reader["skyfile"];								
							}
							break;
					}
						break;
				}       
			}  
			reader.Close();
			// Feed Texture, Sides, Sky and VerticalFactor menu top item
			menuItemTextures.MenuItems[0].Text = textureFileName;
			menuItemSides.MenuItems[0].Text = sidesFileName;
			menuItemSky.MenuItems[0].Text = skyFileName;
			menuItemVerticalFactor.MenuItems[0].Text = "x" + verticalFactor2.ToString(CultureInfo.InvariantCulture);
			menuItemVerticalFactor.MenuItems[0].Checked = true;
		}

		public void InitializeKeyboard()
		{
			keyb = new Microsoft.DirectX.DirectInput.Device(SystemGuid.Keyboard);
			keyb.SetCooperativeLevel(this, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive);
			keyb.Acquire();
		}

 

		// Camera setup
		private void CameraViewSetup()
		{
			float aspectRatio =  (float)device.Viewport.Width / device.Viewport.Height;
			device.Transform.Projection = Matrix.PerspectiveFovLH(fov, aspectRatio, mapWidth == 0 ? 15f : (float)(mapWidth / 30f), mapWidth == 0 ? 5000f : (float)(mapWidth * 3));
			device.Transform.View = Matrix.LookAtLH(new Vector3(0,0,dist), new Vector3(0,0,0), new Vector3(1,0,0));
		}

		// Light setup
		private void LightSetup()
		{
			// Use a white, directional light 
			device.Lights[0].Diffuse = Color.White;
			device.Lights[0].Specular = Color.FromArgb(0x80, 0x80, 0x80); // soft highlight
			device.Lights[0].Type = LightType.Directional;
			// Compute light direction
			if(lightElevation < 0) lightElevation = 0f;
			if(lightElevation > Math.PI / 2) lightElevation = (float)Math.PI / 2;
			Vector3 pos = new Vector3();
			pos.Y = (float)(Math.Sin(lightHeading) * Math.Cos(lightElevation));
			pos.X = (float)(Math.Cos(lightHeading) * Math.Cos(lightElevation));
			pos.Z = (float)Math.Sin(lightElevation);
			pos.Normalize();
			if(fixedLight) 
			{
				Matrix lightTrans = Matrix.Identity;
				lightTrans *= Matrix.RotationZ(angle);
				lightTrans *= Matrix.RotationY(angle2);
				pos.TransformCoordinate(lightTrans);
			}
			device.Lights[0].Direction = -pos;
			device.Lights[0].Update(); 
			device.Lights[0].Enabled = true;
			// Add a little ambient light to the scene
			device.RenderState.Ambient = ambientColor;
			// Material
			device.Material = material;
			// RenderState
			device.RenderState.Lighting = true;
			device.RenderState.SpecularEnable = false;
			// Texture stage states.
			device.TextureState[0].ColorOperation = TextureOperation.Modulate ;
			device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
			device.TextureState[0].ColorArgument2 = TextureArgument.Diffuse;
		}


  
		// Load map resources
		private void LoadMap()
		{
			string errMsg = "";
			this.Cursor = Cursors.WaitCursor;
			// Map name in win title
			this.Text = mapName + " - " + appName + " " + appVersion;
			// Adjust some values
			if(Path.GetExtension(terrainFileName) == ".bil" && mapSpan == 0) mapSpan = GetSpanFromPath(terrainFileName);
			if(worldRadius < 10000) worldRadius *= 1000; // Convert from Km to m
			if(mapSpan != 0) mapWidth = (worldRadius * 2 * Math.PI) / 360 * mapSpan; // Compute map width in meter
			//if(textureFileName == "") textureFileName = "colors/Geo_Water_1.png"; // Default textures
			//if(sidesFileName == "" && textureFileName.IndexOf("colors") != -1) sidesFileName = textureFileName;
			//if(sidesFileName == "") sidesFileName = "colors/Grid_1.png";
			// Update View menu items : Show light, water, fog, section...
			menuItemShowLight.Checked = showLight;
			menuItemShowWater.Checked = showWater;
			menuItemShowFog.Checked = showFog;
			menuItemShowInfo.Checked = showInfo;
			menuItemShowCrosshair.Checked = showCrosshair;
			menuItemShowWorldMap.Checked = showWorldMap;
			menuItemShowSection.Checked = showSection;
			if(textureFileName.IndexOf("colors") != -1) menuItemLightMap.Enabled = false;
			else  menuItemLightMap.Enabled = true;
			// Update Textures menus
			MenuClearCheck(menuItemTextures);
			for(int i = 0; i< menuItemTextures.MenuItems.Count; i++) if(menuItemTextures.MenuItems[i].Text == textureFileName) menuItemTextures.MenuItems[i].Checked = true;
			// Update Sides menus
			MenuClearCheck(menuItemSides);
			for(int i = 0; i< menuItemSides.MenuItems.Count; i++) if(menuItemSides.MenuItems[i].Text == sidesFileName) menuItemSides.MenuItems[i].Checked = true;
			// Update Sky menus
			MenuClearCheck(menuItemSky);
			for(int i = 0; i< menuItemSky.MenuItems.Count; i++) if(menuItemSky.MenuItems[i].Text == skyFileName) menuItemSky.MenuItems[i].Checked = true;
			// Open elevation bitmap
			if(DEM != null) DEM.Dispose();
			int width = 0;
			int height = 0;
			try 
			{
				string filePath = GetFullPathTo(terrainFileName);
				if(Path.GetExtension(terrainFileName) != ".bil") 
				{
					DEM = new Bitmap(filePath); // .jpg, .png
					dem16 = false;
				}
				else
				{
					DEM = BitmapFromBil(filePath); // .bil
					dem16 = true;
				}
				width = DEM.Width;
				height = DEM.Height;
				// Update max and min alt
				FindAltitudeExtremes(DEM);
			}
			catch(Exception e)
			{
				errMsg += ((errMsg == "") ? "" : ", ") + "Error opening DEM file " + terrainFileName + " (" + e.Message + ")";
			}
			try 
			{
				// Create terrain tile mesh list
				BuildTerrainMesh();
			}
			catch(Exception e)
			{
				errMsg += ((errMsg == "") ? "" : ", ") + "Error creating terrain tiles " + " (" + e.Message + ")";
			}
			// Create sides mesh
			try 
			{
				sidesMesh = TerrainSides(device, DEM);
			}
			catch(Exception e)
			{
				errMsg += ((errMsg == "") ? "" : ", ") + "Error building terrain sides " + " (" + e.Message + ")";
			}
			// Create water mesh
			try 
			{
				if(mapMinAlt < 0) 
				{
					int slices = (int)Math.Max(3, (int)(mapSpan / 2.0));
					waterMesh = PlaneMesh(device, DEM, slices, 0, waterColor);
				}
			}
			catch(Exception e)
			{
				errMsg += ((errMsg == "") ? "" : ", ") + "Error building water mesh " + " (" + e.Message + ")";
			}

			// Load terrain texture
			try 
			{
				if(textureFileName != "") 
				{
					texture = TextureLoader.FromFile(device, GetFullPathTo(textureFileName)); 
				} 
				else 
				{
					texture = TextureFromDem(device, DEM); 
				}
			}
			catch(Exception e)
			{
				errMsg += ((errMsg == "") ? "" : ", ") + "Error loading terrain texture " + textureFileName + " (" + e.Message + ")";
			}
			// Load sides texture
			try 
			{
                if (sidesFileName != "")
                {
                    sidesTexture = TextureLoader.FromFile(device, GetFullPathTo(sidesFileName));
                }
                else
                {
                    //sidesTexture = TextureLoader.FromStream(device, System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Geo_Water_1.png"));
                    sidesImage = Images.colors_Grid_1;
                    string tempFile = Path.Combine(Path.GetTempPath(), "sides.png");
                    sidesImage.Save(tempFile);
                    sidesTexture = TextureLoader.FromFile(device, tempFile);
                }
			}
			catch(Exception e)
			{
				errMsg += ((errMsg == "") ? "" : ", ") + "Error loading sides texture " + sidesFileName + " (" + e.Message + ")";
			}
			// Load sky texture and mesh
			try 
			{
				if(skyFileName != "") 
				{
					skyTexture = TextureLoader.FromFile(device, GetFullPathTo(skyFileName));
				}
				// Create sky dome mesh
				float radius = (float)(Math.Max(width, height) / 2) + 70;
				if(mapWidth != 0) 
				{
					radius = (float)(mapWidth / 2  * Math.Sqrt(2));
					radius = (float)Math.Sqrt(radius * radius + mapMaxAlt * mapMaxAlt);
				}
				skyMesh = TexturedDome(device, radius, 64, 32);
			}
			catch(Exception e)
			{
				errMsg += ((errMsg == "") ? "" : ", ") + "Error loading sky " + skyFileName + " (" + e.Message + ")";
			}
			// Default material
			material = new Material();
			material.Diffuse = Color.FromArgb(0xff, 0xff, 0xff, 0xff);
			material.Ambient = Color.White;
			material.Specular = Color.White;
			material.SpecularSharpness = 30.0f;  // thin highlight (smaller = larger)
			// Adjust distance and offset according to map size
			if(mapWidth > 0) 
			{
				if(dist < mapWidth / 2 || dist > mapWidth * 3) 
				{
					dist = (float)mapWidth * 1.5f;
                    initialDist = dist;
				}
				if(Math.Abs(dx) > mapWidth || Math.Abs(dy) > mapWidth) dx = dy = 0;
			}
			else
			{
				if(dist < width / 2 || dist > width * 3) 
				{
					dist = (float)(width * 1.5);
                    initialDist = dist;
				}
				if(Math.Abs(dx) > width || Math.Abs(dy) > height) dx = dy = 0;
			}
			this.Cursor = Cursors.Default;
			// Force refresh
			redraw = true;
			// Report errors
			if(errMsg != "") 
			{
				// TODO : find out why the MessageBox appears behind the main window...
				//MessageBox.Show(errMsg, "Error loading map... ", MessageBoxButtons.OK, MessageBoxIcon.Error);
				this.Text = mapName + " (Error loading map !) - " + appName + " " + appVersion;
			}
		}

		public string GetFullPathTo(string fileName)
		{
			string filePath = fileName;
			if(!Path.IsPathRooted(filePath)) 
			{
				filePath = Path.Combine(mapListPath, fileName);
				if(!File.Exists(filePath)) filePath = Path.Combine(Application.ExecutablePath, fileName);
				if(!File.Exists(filePath)) filePath = fileName;
			}
			return filePath;
		}

		// Free map resources
		private void DisposeMap()
		{
			if(skyMesh != null) 
			{
				skyMesh.Dispose();
				skyMesh = null;
			}
			if(sidesMesh != null) 
			{
				sidesMesh.Dispose();
				sidesMesh = null;
			}
			if(sectionMesh != null) 
			{
				sectionMesh.Dispose();
				sectionMesh = null;
			}
			if(waterMesh != null) 
			{
				waterMesh.Dispose();
				waterMesh = null;
			}
			if(texture != null) 
			{
				texture.Dispose();
				texture = null;
			}
			if(skyTexture != null) 
			{
				skyTexture.Dispose();
				skyTexture = null;
			}
			if(sidesTexture != null) 
			{
				sidesTexture.Dispose();
				sidesTexture = null;
			}
			if(lightMapTexture != null) 
			{
				lightMapTexture.Dispose();
				lightMapTexture = null;
			}
			if(meshCount > 0) 
			{
				DisposeTerrainMesh();
			}
			this.showTransparentTerrain = false;
		}

		public void DisposeTerrainMesh() 
		{
			if(meshCount > 0) 
			{
				for(int i = 0; i < meshCount; i++) 
				{
					meshList[i].Dispose();
					meshList[i] = null;
				}
				meshCount = 0;
			}
		}

		// Frame loop
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			//redraw navigation panel
            panel1.Refresh();
            
            if(redraw) 
			{
				try 
				{
					redraw = false;

					device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, fogColor , 1.0f, 0);
					device.BeginScene();
				
					// Rendering here
					CameraViewSetup();
					device.VertexFormat = CustomVertex.PositionNormalTextured.Format;
					device.RenderState.ZBufferEnable = true;

					// Translation and Orientation angle / angle2
					if(angle2 < 0) angle2 = 0;
					if(angle2 > Math.PI / 2) angle2 = (float)(Math.PI / 2);
					device.Transform.World = Matrix.Translation(dx, dy, dz);
					device.Transform.World *= Matrix.RotationZ(angle);
					device.Transform.World *= Matrix.RotationY(angle2);

	 
					// Fog
					if(showFog) 
					{
						device.RenderState.FogEnable = true;
						device.RenderState.FogColor = fogColor;
						device.RenderState.FogTableMode = FogMode.Linear;
						device.RenderState.FogStart = mapWidth == 0 ? 60 : (float)mapWidth / 8;
						device.RenderState.FogEnd = mapWidth == 0 ? 600 : (float)mapWidth * 2;
					} 
					else 
					{
						device.RenderState.FogEnable = false;
					}

					// Light
					if(showLight) LightSetup();
					else device.RenderState.Lighting = false;

					// Draw cross section
					if(sidesTexture != null && sectionMesh  != null) 
					{
						sectionMesh.Dispose();
						sectionMesh = TerrainSection(device, DEM);
						device.SetTexture(0,sidesTexture);
						sectionMesh.DrawSubset(0);
					}

					// Terrain transparency
					if(showTransparentTerrain) 
					{
						device.RenderState.TextureFactor = Color.FromArgb(terrainOpacity, 0, 0, 0).ToArgb();
						device.TextureState[0].AlphaOperation = TextureOperation.BlendFactorAlpha;
						device.TextureState[0].AlphaArgument2 = TextureArgument.TFactor;
						device.RenderState.CullMode = Cull.None; // Render both sides 
					}

					// Draw terrain tiles mesh
					if(texture != null && meshCount > 0) 
					{
						device.SetTexture(0,texture);
						if(lightMapTexture != null && textureFileName.IndexOf("colors") == -1) 
						{
							device.SetTexture(1,lightMapTexture);
							device.TextureState[1].ColorOperation = TextureOperation.Modulate;
							device.TextureState[1].AlphaOperation = TextureOperation.Disable;
							device.TextureState[1].ColorArgument1 = TextureArgument.TextureColor;
							device.TextureState[1].ColorArgument2 = TextureArgument.Current;
							device.TextureState[1].TextureCoordinateIndex = 0;
						}
						for(int i = 0; i < meshCount; i++) meshList[i].DrawSubset(0);
						if(lightMapTexture != null) 
						{
							device.SetTexture(1,null);
							device.TextureState[1].ColorOperation = TextureOperation.Disable;
						}
					}

					// Draw terrain sides
					if(sidesTexture != null && sidesMesh  != null) 
					{
						device.SetTexture(0,sidesTexture);
						sidesMesh.DrawSubset(0);
					}

					// Turn off transparency
					if(showTransparentTerrain) 
					{
						device.TextureState[0].AlphaOperation = TextureOperation.SelectArg1;
						device.RenderState.CullMode = Cull.CounterClockwise; 
					}

					// Draw water
					if(showWater && waterMesh != null)
					{
						device.SetTexture(0, null);
						device.TextureState[0].ColorArgument1 = TextureArgument.Diffuse;
						device.TextureState[0].AlphaArgument1 = TextureArgument.Diffuse;
						waterMesh.DrawSubset(0);
						device.TextureState[0].ColorArgument1 = TextureArgument.TextureColor;
						device.TextureState[0].AlphaArgument1 = TextureArgument.TextureColor;
					}

					// Draw sky
					if(skyTexture != null && skyMesh != null) 
					{
						device.RenderState.FogEnable = false;
						device.RenderState.Lighting = false;
						device.SetTexture(0,skyTexture);
						skyMesh.DrawSubset(0);
					}

					// Draw info text
					if(showInfo) DrawInfoText();

					// Draw crosshair
					if(showCrosshair) DrawCrossHairs();

					// Draw world map
					//if(showWorldMap && mapSpan != 0) worldMap.Render(device, (float)mapLat, (float)mapLon);

					// Done rendering
					device.EndScene();
					device.Present();
					System.Threading.Thread.Sleep(50);
				}
				catch
				{
					// do nothing...
				}
			}
			else // No redraw
			{
				device.BeginScene();
				device.EndScene();
				device.Present();
				System.Threading.Thread.Sleep(50);
			}
			this.Invalidate();
			// Read keyboard
			if(this.Focused) ReadKeyboard();
			// Slow spin
			if(spin) {angle-=0.002f; redraw = true;}
 
            //arrow actions
            ReadNavigation();
		}

        // Navigation arrow actions
        private void ReadNavigation()
        {
            //rotate map - using arrow
            if (navigationType == Navigation.Left)
            {
                angle -= 0.02f;
                RotateArrow(angle);
                redraw = true;
            }
            if (navigationType == Navigation.Right)
            {
                angle += 0.02f;
                RotateArrow(angle);
                redraw = true;
            }
            if (navigationType == Navigation.Up)
            {
                angle2 += 0.02f;
                redraw = true;
            }
            if (navigationType == Navigation.Down)
            {
                angle2 -= 0.02f;
                redraw = true;
            }
            if (navigationType == Navigation.ZoomIn)
            {
                dist -= dist * 0.02f;
                redraw = true;
            }
            if (navigationType == Navigation.ZoomOut)
            {
                dist += dist * 0.02f;
                redraw = true;
            }
        }

		// Keyboard actions
		private void ReadKeyboard()
		{
			KeyboardState keys = keyb.GetCurrentKeyboardState();
			bool shift = keys[Key.LeftShift]  || keys[Key.RightShift];
			bool ctrl = keys[Key.LeftControl]  || keys[Key.RightControl];
			double moveFactor = dist * 0.01f;
			// Toggle Light
			if (keys[Key.L] && !shift && !ctrl)	
			{
				showLight = true;
				menuItemShowLight.Checked = showLight;
				redraw = true;
			}
			if (keys[Key.L] && shift && !ctrl)	
			{
				showLight = false;
				menuItemShowLight.Checked = showLight;
				redraw = true;
			}
			// Create/delete light map
			if (keys[Key.L] && !shift && ctrl)	
			{
				if(textureFileName.IndexOf("colors") == -1) // only on textured maps
				{
					if(lightMapTexture != null) lightMapTexture.Dispose();
					lightMapTexture = null;
					this.Cursor = Cursors.WaitCursor;
					lightMapTexture = LightMap(device, DEM, 1);
					this.Cursor = Cursors.Default;
					redraw = true;
				}
			}
			if (keys[Key.L]  && shift && ctrl)	
			{
				if(lightMapTexture != null) lightMapTexture.Dispose();
				lightMapTexture = null;
				redraw = true;
			}
			// Create/delete section mesh
			if (keys[Key.S]  && !shift && !ctrl)	
			{
				if(sectionMesh != null) sectionMesh.Dispose();
				sectionMesh = null;
				showSection = true;
				sectionMesh = TerrainSection(device, DEM);
				showTransparentTerrain = true;
				menuItemShowSection.Checked = showSection;
				redraw = true;
			}
			if (keys[Key.S]  && shift && !ctrl)	
			{
				if(sectionMesh != null) sectionMesh.Dispose();
				sectionMesh = null;
				showSection = false;
				showTransparentTerrain = false;
				menuItemShowSection.Checked = showSection;
				redraw = true;
			}
			// Toggle Fog
			if (keys[Key.F] && !shift)	
			{
				showFog = true;
				menuItemShowFog.Checked = showFog;
				redraw = true;
			}
			if (keys[Key.F] && shift)	
			{
				showFog = false;
				menuItemShowFog.Checked = showFog;
				redraw = true;
			}
			// Toggle map spin
			if (keys[Key.Space])	
			{
				spin = false;
				menuItemShowSpin.Checked = spin;
				redraw = true;
			}
			if (keys[Key.Return])	
			{
				spin = true;
				menuItemShowSpin.Checked = spin;
				redraw = true;
			}

			// Rotate map
			if (keys[Key.RightArrow] && !shift)	
			{
				angle -= 0.02f;
				redraw = true;
			}
			if (keys[Key.LeftArrow] && !shift)
			{
				angle += 0.02f;
				redraw = true;
			}
			if (keys[Key.UpArrow] && shift)
			{
				angle2 += 0.02f;
				redraw = true;
			}
			if (keys[Key.DownArrow] && shift)
			{
				angle2 -= 0.02f;
				redraw = true;
			}
			// Move map
			if (keys[Key.RightArrow] && shift)	
			{
				dx -= (float)(Math.Sin(angle) * moveFactor);
				dy -= (float)(Math.Cos(angle) * moveFactor);
				redraw = true;
			}
			if (keys[Key.LeftArrow] && shift)
			{
				dx += (float)(Math.Sin(angle) * moveFactor);
				dy += (float)(Math.Cos(angle) * moveFactor);
				redraw = true;
			}
			if (keys[Key.UpArrow] && !shift)
			{
				dx -= (float)(Math.Cos(angle) * moveFactor);
				dy += (float)(Math.Sin(angle) * moveFactor);
				redraw = true;
			}
			if (keys[Key.DownArrow] && !shift)
			{
				dx += (float)(Math.Cos(angle) * moveFactor);
				dy -= (float)(Math.Sin(angle) * moveFactor);
				redraw = true;
			}
			// Change Distance
			if (keys[Key.NumPadPlus] && !shift && !ctrl)	
			{
				dist -= dist * 0.02f;
				redraw = true;
			}
			if (keys[Key.NumPadMinus] && !shift && !ctrl)
			{
				dist += dist * 0.02f;
				redraw = true;
			}
			// Change FOV
			if (keys[Key.NumPadPlus] && shift && !ctrl)	
			{
				fov -= 0.01f;
				redraw = true;
			}
			if (keys[Key.NumPadMinus] && shift && !ctrl)
			{
				fov += 0.01f;
				redraw = true;
			}
			// Change alt scale factor (vert exaggeration)
			if ((keys[Key.X] || keys[Key.NumPadPlus]) && !shift && ctrl)	
			{
				verticalFactor *= 1.3333f;
				MenuClearCheck(menuItemVerticalFactor);
				menuItemVerticalFactor.MenuItems[0].Text = "x" + verticalFactor.ToString(CultureInfo.InvariantCulture);
				menuItemVerticalFactor.MenuItems[0].Checked = true;
				this.Cursor = Cursors.WaitCursor;
				// Rebuilt terrain mesh
				DisposeTerrainMesh();
				BuildTerrainMesh();
				if(sidesMesh != null) sidesMesh.Dispose();
				sidesMesh = TerrainSides(device, DEM);
				this.Cursor = Cursors.Default;
				redraw = true;
			}
			if ((keys[Key.Z] || keys[Key.NumPadMinus]) && !shift && ctrl)
			{
				verticalFactor *= 0.75f;
				MenuClearCheck(menuItemVerticalFactor);
				menuItemVerticalFactor.MenuItems[0].Text = "x" + verticalFactor.ToString(CultureInfo.InvariantCulture);
				menuItemVerticalFactor.MenuItems[0].Checked = true;
				this.Cursor = Cursors.WaitCursor;
				// Rebuilt terrain mesh
				DisposeTerrainMesh();
				BuildTerrainMesh();
				if(sidesMesh != null) sidesMesh.Dispose();
				sidesMesh = TerrainSides(device, DEM);
				this.Cursor = Cursors.Default;
				redraw = true;
			}
		}

		// Mouse tracking
		protected override void OnMouseWheel(MouseEventArgs e)
		{
			// Zoom in and out
			this.dist -= this.dist * ( e.Delta/3000.0f );
			redraw = true;
		}
		protected override void OnMouseDown(MouseEventArgs e)
		{
			// Save mouse position and map transform values at that point
			mouseDownStartPosition.X = e.X;
			mouseDownStartPosition.Y = e.Y;
			mouseDownStartDx = dx;
			mouseDownStartDy = dy;
			mouseDownStartAngle = angle;
			mouseDownStartAngle2 = angle2;
			mouseDownLightAngle = lightHeading;
			mouseDownLightAngle2 = lightElevation;
		}

		protected override void OnMouseUp(MouseEventArgs e)
		{
			mouseDownStartPosition = Point.Empty;
			redraw = true;
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			// Track mouse pos
			mouse.X = e.X;
			mouse.Y = e.Y;
			if(showInfo) redraw = true; // force info display
			if(mouseDownStartPosition == Point.Empty)
				return;
			// Mouse drag
			bool isMouseLeftButtonDown = ((int)e.Button & (int)MouseButtons.Left) != 0;
			bool isMouseRightButtonDown = ((int)e.Button & (int)MouseButtons.Right) != 0;
			double dxMouse = this.mouseDownStartPosition.X - e.X;
			double dyMouse = this.mouseDownStartPosition.Y - e.Y;
			if(isMouseLeftButtonDown && !isMouseRightButtonDown) 
			{
				// Move map
				double moveFactor = dist * 0.001f;
				dx = mouseDownStartDx - (float)(Math.Sin(angle) * dxMouse * moveFactor) + (float)(Math.Cos(angle) * dyMouse * moveFactor);
				dy = mouseDownStartDy - (float)(Math.Cos(angle) * dxMouse * moveFactor) - (float)(Math.Sin(angle) * dyMouse * moveFactor);
				redraw = true;
			}
			if(isMouseRightButtonDown && !isMouseLeftButtonDown) 
			{
				// Rotate map
				double spinFactor = 0.003f;
				angle = mouseDownStartAngle + (float)(dxMouse * spinFactor * rightClickFactor);
				angle2 = mouseDownStartAngle2 + (float)(dyMouse * spinFactor * rightClickFactor);
				redraw = true;
			}
			if(isMouseRightButtonDown && isMouseLeftButtonDown) 
			{
				// Rotate light
				double spinFactor = 0.003f;
				lightHeading = mouseDownLightAngle + (float)(dxMouse * spinFactor);
				lightElevation = mouseDownLightAngle2 + (float)(dyMouse * spinFactor);
				redraw = true;
			}
		}

		public void BuildTerrainMesh()
		{
			// Create terrain tile mesh list
			int tileCols = (int)Math.Ceiling((float)(DEM.Width - 1) / (float)meshWidth); // using intervals
			int tileRows = (int)Math.Ceiling((float)(DEM.Height - 1) / (float)meshWidth);
			for (int col = 0; col < tileCols; col++) 
			{
				for (int row = 0; row < tileRows; row++) 
				{
					meshList[meshCount++] = TerrainTile(device, DEM, col, row);
				}
			}

		}

		/// <summary>
		/// Creates a terrain elevation mesh / tile for a portion of the DEM data
		/// Tile col and row are based on global meshWidth.
		/// </summary>
		/// <param name="device">The current direct3D drawing device.</param>
		/// <param name="DEM">The elevation DEM bitmap</param>
		/// <param name="col">Column index (0 based)</param>
		/// <param name="row">Row index (0 based)</param>
		/// <returns>The tile mesh</returns>
		private Mesh TerrainTile(Microsoft.DirectX.Direct3D.Device device, Bitmap DEM, int col, int row)
		{
			// Elevation bitmap
			int demWidth = DEM.Width;
			int demHeight = DEM.Height;
			// Unit square
			int squareSize = 1;
			if(mapWidth != 0) squareSize = (int)(mapWidth / (demWidth - 1));
			// Data range - get one extra row/col around the tile for normal computation
			int startX = meshWidth * col - 1;
			int startY = meshWidth * row - 1;
			int endX = Math.Min(demWidth, startX + meshWidth + 2);
			int endY = Math.Min(demHeight, startY + meshWidth + 2);
			int width = endX - startX + 1;
			int height = endY - startY + 1;
			// Mesh values
			int numVertices = width * height;
			int numFaces	= (width-1) * (height-1) * 2;
			int indexCount	= numFaces * 3;

			// Create mesh
			Mesh mesh = new Mesh(numFaces,numVertices,MeshFlags.Managed,CustomVertex.PositionNormalTextured.Format,device);

			// Get the original mesh vertex buffer.
			int [] ranks = new int[1];
			ranks[0] = mesh.NumberVertices;
			System.Array arr = mesh.VertexBuffer.Lock(0,typeof(CustomVertex.PositionNormalTextured),LockFlags.None,ranks);

			// Set the vertex buffer
			int vertIndex = 0;
			int offsetX = (demWidth - 1) * squareSize / 2;
			int offsetY = (demHeight - 1) * squareSize / 2;
			for(int y = startY; y <= endY; y++)
			{
				double vy = y * squareSize - offsetY;
				for(int x = startX; x <= endX; x++)
				{
					CustomVertex.PositionNormalTextured pnt = new CustomVertex.PositionNormalTextured();
					double vx = x * squareSize - offsetX;
					float alt = (float)GetAlt(DEM, x, y);
					if(mapSpan != 0) // Do ortho projection
					{
						Vector2 latLon = this.SampleToLatLon(x, y, demWidth, mapLat, mapLon, mapSpan, mapHeading);
						Vector2 proj = this.ProjectToPlane(latLon.X, latLon.Y, mapLat, mapLon, worldRadius);
						vx = proj.X;
						vy = proj.Y;
					}
					pnt.X = (float)vx;
					pnt.Y = (float)vy;
					pnt.Z = alt * verticalFactor;
					// Texture coord
					pnt.Tu = (float)((float)x / (float)(demWidth - 1));
					pnt.Tv = (float)((float)y / (float)(demHeight - 1));
					if(textureFileName.StartsWith("colors")) 
					{
						pnt.Tu = 0f;
						pnt.Tv = GetTvFromAltitude((int)alt, textureFileName);
					}

					arr.SetValue(pnt,vertIndex++);
				}
			}
			mesh.VertexBuffer.Unlock();

			// Set index buffer
			ranks[0]=indexCount;
			arr = mesh.LockIndexBuffer(typeof(short),LockFlags.None,ranks);
			int i=0;
			short bottomVertex = 0;
			short topVertex = 0;
			for(short y = 0; y < height - 1; y++)
			{
				topVertex = (short)(width * y);
				bottomVertex = (short)(topVertex + width);
				for(int x = 0; x < width - 1; x++)
				{
					arr.SetValue(bottomVertex,i++);
					arr.SetValue(topVertex,i++);
					arr.SetValue((short)(topVertex+1),i++);
					arr.SetValue(bottomVertex,i++);
					arr.SetValue((short)(topVertex+1),i++);
					arr.SetValue((short)(bottomVertex+1),i++);
					bottomVertex++;
					topVertex++;
				}
			}
			mesh.IndexBuffer.SetData(arr,0,LockFlags.None);
			mesh.ComputeNormals();

			// Fold back extra vertices
			ranks[0] = mesh.NumberVertices;
			arr = mesh.VertexBuffer.Lock(0,typeof(CustomVertex.PositionNormalTextured),LockFlags.None,ranks);
			for(int y = 0; y < height; y++)
			{
				for(int x = 0; x < width; x++)
				{
					vertIndex = y * width + x;
					int refIndex = vertIndex;
					if(y == 0) refIndex += width;
					if(y == height - 1) refIndex -= width;
					if(x == 0) refIndex += 1;
					if(x == width - 1) refIndex -= 1;
					CustomVertex.PositionNormalTextured pnt = (CustomVertex.PositionNormalTextured)arr.GetValue(refIndex);
					arr.SetValue(pnt,vertIndex);
				}
			}
			mesh.VertexBuffer.Unlock();

			return mesh;
		}

		/// <summary>
		/// Creates the terrain sides mesh around the DEM data
		/// </summary>
		/// <param name="device">The current direct3D drawing device.</param>
		/// <param name="DEM">The elevation DEM bitmap</param>
		/// <returns>The mesh</returns>
		private Mesh TerrainSides(Microsoft.DirectX.Direct3D.Device device, Bitmap DEM)
		{
			// Elevation bitmap
			int width = DEM.Width;
			int height = DEM.Height;
			// Unit square
			int squareSize = 1;
			if(mapWidth != 0) squareSize = (int)(mapWidth / (width - 1));

			// Mesh values
			int numVertices = (width + height) * 2 * 2;
			int numFaces	= (width-1 + height-1) * 2 * 2;
			int indexCount	= numFaces * 3;

			// Create mesh
			Mesh mesh = new Mesh(numFaces,numVertices,MeshFlags.Managed,CustomVertex.PositionNormalTextured.Format,device);

			// Get the original mesh vertex buffer.
			int [] ranks = new int[1];
			ranks[0] = mesh.NumberVertices;
			System.Array arr = mesh.VertexBuffer.Lock(0,typeof(CustomVertex.PositionNormalTextured),LockFlags.None,ranks);

			// Set the vertex buffer
			int vertIndex = 0;
			int offsetX = (width - 1) * squareSize / 2;
			int offsetY = (height - 1) * squareSize / 2;
			int x, y;
			double vx, vy;
			CustomVertex.PositionNormalTextured pnt;
			// North side
			y = 0;
			vy = y * squareSize - offsetY;
			for(x = 0; x < width; x++)
			{
				vx = x * squareSize - offsetX;
				float alt = (float)GetAlt(DEM, x, y);
				if(mapSpan != 0) // Do ortho projection
				{
					Vector2 latLon = this.SampleToLatLon(x, y, width, mapLat, mapLon, mapSpan, mapHeading);
					Vector2 proj = this.ProjectToPlane(latLon.X, latLon.Y, mapLat, mapLon, worldRadius);
					vx = proj.X;
					vy = proj.Y;
				}
				pnt = new CustomVertex.PositionNormalTextured(); // Terrain
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = alt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = GetTvFromAltitude((int)alt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
				pnt = new CustomVertex.PositionNormalTextured(); // Bottom
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = (float)mapMinAlt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = 1f;
				pnt.Tv = GetTvFromAltitude((int)mapMinAlt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
			}
			// East side
			x = width-1;
			vx = x * squareSize - offsetX;
			for(y = 0; y < height; y++)
			{
				vy = y * squareSize - offsetY;
				float alt = (float)GetAlt(DEM, x, y);
				if(mapSpan != 0) // Do ortho projection
				{
					Vector2 latLon = this.SampleToLatLon(x, y, width, mapLat, mapLon, mapSpan, mapHeading);
					Vector2 proj = this.ProjectToPlane(latLon.X, latLon.Y, mapLat, mapLon, worldRadius);
					vx = proj.X;
					vy = proj.Y;
				}
				pnt = new CustomVertex.PositionNormalTextured(); // Terrain
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = alt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = GetTvFromAltitude((int)alt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
				pnt = new CustomVertex.PositionNormalTextured(); // Bottom
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = (float)mapMinAlt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = GetTvFromAltitude((int)mapMinAlt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
			}
			// South side reversed
			y = height - 1;
			vy = y * squareSize - offsetY;
			for(x = width-1; x >= 0; x--)
			{
				vx = x * squareSize - offsetX;
				float alt = (float)GetAlt(DEM, x, y);
				if(mapSpan != 0) // Do ortho projection
				{
					Vector2 latLon = this.SampleToLatLon(x, y, width, mapLat, mapLon, mapSpan, mapHeading);
					Vector2 proj = this.ProjectToPlane(latLon.X, latLon.Y, mapLat, mapLon, worldRadius);
					vx = proj.X;
					vy = proj.Y;
				}
				pnt = new CustomVertex.PositionNormalTextured(); // Terrain
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = alt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = GetTvFromAltitude((int)alt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
				pnt = new CustomVertex.PositionNormalTextured(); // Bottom
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = (float)mapMinAlt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = GetTvFromAltitude((int)mapMinAlt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
			}
			// West side reversed
			x = 0;
			vx = x * squareSize - offsetX;
			for(y = height-1; y >= 0; y--)
			{
				vy = y * squareSize - offsetY;
				float alt = (float)GetAlt(DEM, x, y);
				if(mapSpan != 0) // Do ortho projection
				{
					Vector2 latLon = this.SampleToLatLon(x, y, width, mapLat, mapLon, mapSpan, mapHeading);
					Vector2 proj = this.ProjectToPlane(latLon.X, latLon.Y, mapLat, mapLon, worldRadius);
					vx = proj.X;
					vy = proj.Y;
				}
				pnt = new CustomVertex.PositionNormalTextured(); // Terrain
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = alt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = GetTvFromAltitude((int)alt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
				pnt = new CustomVertex.PositionNormalTextured(); // Bottom
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = (float)mapMinAlt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = GetTvFromAltitude((int)mapMinAlt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
			}
			mesh.VertexBuffer.Unlock();

			// Set index buffer
			ranks[0]=indexCount;
			arr = mesh.LockIndexBuffer(typeof(short),LockFlags.None,ranks);
			int i=0;
			short bottomVertex = 0;
			short topVertex = 0;
			// North
			topVertex = (short)0;
			bottomVertex = (short)(topVertex + 1);
			for(x = 0; x < width - 1; x++)
			{
				arr.SetValue(topVertex,i++);
				arr.SetValue(bottomVertex,i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				arr.SetValue(topVertex,i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				arr.SetValue((short)(topVertex+2),i++);
				bottomVertex += 2;
				topVertex += 2;
			}
			// East
			topVertex = (short)(width * 2);
			bottomVertex = (short)(topVertex + 1);
			for(y = 0; y < height - 1; y++)
			{
				arr.SetValue(topVertex,i++);
				arr.SetValue(bottomVertex,i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				arr.SetValue(topVertex,i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				arr.SetValue((short)(topVertex+2),i++);
				bottomVertex += 2;
				topVertex += 2;
			}
			// South
			topVertex = (short)((width + height) * 2);
			bottomVertex = (short)(topVertex + 1);
			for(x = 0; x < width - 1; x++)
			{
				arr.SetValue(topVertex,i++);
				arr.SetValue(bottomVertex,i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				arr.SetValue(topVertex,i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				arr.SetValue((short)(topVertex+2),i++);
				bottomVertex += 2;
				topVertex += 2;
			}
			// West
			topVertex = (short)((width + height + width) * 2);
			bottomVertex = (short)(topVertex + 1);
			for(y = 0; y < height - 1; y++)
			{
				arr.SetValue(topVertex,i++);
				arr.SetValue(bottomVertex,i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				arr.SetValue(topVertex,i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				arr.SetValue((short)(topVertex+2),i++);
				bottomVertex += 2;
				topVertex += 2;
			}
			mesh.IndexBuffer.SetData(arr,0,LockFlags.None);
			mesh.ComputeNormals();

			return mesh;
		}

		/// <summary>
		/// Creates the terrain section mesh across the DEM data
		/// </summary>
		/// <param name="device">The current direct3D drawing device.</param>
		/// <param name="DEM">The elevation DEM bitmap</param>
		/// <returns>The mesh</returns>
		private Mesh TerrainSection(Microsoft.DirectX.Direct3D.Device device, Bitmap DEM)
		{
			// Elevation bitmap
			int width = DEM.Width;
			int height = DEM.Height;
			// Unit square
			int squareSize = 1;
			if(mapWidth != 0) squareSize = (int)(mapWidth / (width - 1));

			// Mesh values
			int length = (int)Math.Sqrt(width * width + height * height);
			int numVertices = length * 2;
			int numFaces	= (length - 1) * 2;
			int indexCount	= numFaces * 3;

			// Create mesh
			Mesh mesh = new Mesh(numFaces,numVertices,MeshFlags.Managed,CustomVertex.PositionNormalTextured.Format,device);

			// Get the original mesh vertex buffer.
			int [] ranks = new int[1];
			ranks[0] = mesh.NumberVertices;
			System.Array arr = mesh.VertexBuffer.Lock(0,typeof(CustomVertex.PositionNormalTextured),LockFlags.None,ranks);

			// Set the vertex buffer
			int vertIndex = 0;
			int offsetX = (width - 1) * squareSize / 2;
			int offsetY = (height - 1) * squareSize / 2;
			// Start at one end of section
			float x = -(float)((float)length / 2f * (float)Math.Sin(angle)) + (float)width / 2f;
			float y = -(float)((float)length / 2f * (float)Math.Cos(angle)) + (float)height / 2f;
			// Step increments
			float stepX = (float)Math.Sin(angle);
			float stepY = (float)Math.Cos(angle);
			// Translate
			x -= (dx / squareSize);
			y -= (dy / squareSize);
			double vx, vy;
			CustomVertex.PositionNormalTextured pnt;

			for(int v = 0; v < length; v++)
			{
				vx = x * squareSize - offsetX;
				vy = y * squareSize - offsetY;
				if(mapSpan != 0) // Do ortho projection
				{
					Vector2 latLon = this.SampleToLatLon(x, y, width, mapLat, mapLon, mapSpan, mapHeading);
					Vector2 proj = this.ProjectToPlane(latLon.X, latLon.Y, mapLat, mapLon, worldRadius);
					vx = proj.X;
					vy = proj.Y;
				}
				float alt = (float)GetAlt(DEM, x, y);
				if(x < 0 || x > width-1 || y < 0 || y > height-1) alt = (float)mapMinAlt * verticalFactor;
				pnt = new CustomVertex.PositionNormalTextured(); // Terrain
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = alt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = GetTvFromAltitude((int)alt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
				pnt = new CustomVertex.PositionNormalTextured(); // Bottom
				pnt.X = (float)vx;
				pnt.Y = (float)vy;
				pnt.Z = (float)mapMinAlt * verticalFactor;
				pnt.Tu = 0f;
				pnt.Tv = GetTvFromAltitude((int)mapMinAlt, sidesFileName);
				arr.SetValue(pnt,vertIndex++);
				x += stepX;
				y += stepY;
			}
			mesh.VertexBuffer.Unlock();

			// Set index buffer
			ranks[0]=indexCount;
			arr = mesh.LockIndexBuffer(typeof(short),LockFlags.None,ranks);
			int i=0;
			short bottomVertex = 0;
			short topVertex = 0;

			topVertex = (short)0;
			bottomVertex = (short)(topVertex + 1);
			for(int v = 0; v < length - 1; v++)
			{
				arr.SetValue(bottomVertex,i++);
				arr.SetValue(topVertex,i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				arr.SetValue(topVertex,i++);
				arr.SetValue((short)(topVertex+2),i++);
				arr.SetValue((short)(bottomVertex+2),i++);
				bottomVertex += 2;
				topVertex += 2;
			}
			mesh.IndexBuffer.SetData(arr,0,LockFlags.None);
			mesh.ComputeNormals();

			return mesh;
		}

		/// <summary>
		/// Creates a PositionNormalTextured dome above X-Y plane
		/// </summary>
		/// <param name="device">The current direct3D drawing device.</param>
		/// <param name="radius">The dome's radius</param>
		/// <param name="slices">Number of slices (Horizontal resolution).</param>
		/// <param name="stacks">Number of stacs. (Vertical resolution)</param>
		/// <returns></returns>
		/// <remarks>
		/// Number of vertices in the dome will be (slices+1)*(stacks+1)<br/>
		/// Number of faces	:slices*stacks*2
		/// Number of Indexes	: Number of faces * 3;
		/// </remarks>
		private Mesh TexturedDome(Microsoft.DirectX.Direct3D.Device device, float radius, int slices, int stacks)
		{
			int numVertices = (slices+1)*(stacks+1);
			int numFaces	= slices*stacks*2;
			int indexCount	= numFaces * 3;

			Mesh mesh = new Mesh(numFaces,numVertices,MeshFlags.Managed,CustomVertex.PositionNormalTextured.Format,device);

			// Get the original sphere's vertex buffer.
			int [] ranks = new int[1];
			ranks[0] = mesh.NumberVertices;
			System.Array arr = mesh.VertexBuffer.Lock(0,typeof(CustomVertex.PositionNormalTextured),LockFlags.None,ranks);

			// Set the vertex buffer
			int vertIndex=0;
			for(int stack=0;stack<=stacks;stack++)
			{
				double latitude = (float)stack/stacks*(float)90.0;
				for(int slice=0;slice<=slices;slice++)
				{
					CustomVertex.PositionNormalTextured pnt = new CustomVertex.PositionNormalTextured();
					double longitude = 180 - ((float)slice/slices*(float)360);
					Vector3 v = SphericalToCartesian( latitude, longitude, radius);
					pnt.X = v.X;
					pnt.Y = v.Y;
					pnt.Z = v.Z;
					pnt.Tu = (float)slice/slices;
					pnt.Tv = 0.99f-(float)stack/stacks;
					arr.SetValue(pnt,vertIndex++);
				}
			}

			mesh.VertexBuffer.Unlock();

			// Set index buffer
			ranks[0]=indexCount;
			arr = mesh.LockIndexBuffer(typeof(short),LockFlags.None,ranks);
			int i=0;
			short bottomVertex = 0;
			short topVertex = 0;
			for(short x=0;x<stacks;x++)
			{
				bottomVertex = (short)((slices+1)*x);
				topVertex = (short)(bottomVertex + slices + 1);
				for(int y=0;y<slices;y++)
				{
					arr.SetValue(bottomVertex,i++);
					arr.SetValue((short)(topVertex+1),i++);
					arr.SetValue(topVertex,i++);
					arr.SetValue(bottomVertex,i++);
					arr.SetValue((short)(bottomVertex+1),i++);
					arr.SetValue((short)(topVertex+1),i++);
					bottomVertex++;
					topVertex++;
				}
			}
			mesh.IndexBuffer.SetData(arr,0,LockFlags.None);
			//mesh.ComputeNormals();

			return mesh;
		}

		// Make a planar mesh at some altitude
		private Mesh PlaneMesh(Microsoft.DirectX.Direct3D.Device device, Bitmap DEM, int slices, int alt, Color color)
		{
			// Elevation bitmap
			int demWidth = DEM.Width;
			int demHeight = DEM.Height;
			// Unit square
			int squareSize = demWidth / slices;
			if(mapWidth != 0) squareSize = (int)(mapWidth / slices);

			int stacks = slices;
			int numVertices = (slices+1)*(stacks+1);
			int numFaces	= slices*stacks*2;
			int indexCount	= numFaces * 3;

			Mesh mesh = new Mesh(numFaces,numVertices,MeshFlags.Managed,CustomVertex.PositionNormalColored.Format,device);

			// Get the original mesh vertex buffer.
			int [] ranks = new int[1];
			ranks[0] = mesh.NumberVertices;
			System.Array arr = mesh.VertexBuffer.Lock(0,typeof(CustomVertex.PositionNormalColored),LockFlags.None,ranks);

			// Set the vertex buffer
			int vertIndex = 0;
			int offsetX = slices * squareSize / 2;
			int offsetY = stacks * squareSize / 2;
			for(int y = 0; y <= slices; y++)
			{
				double vy = y * squareSize - offsetY;
				for(int x = 0; x <= stacks; x++)
				{
					CustomVertex.PositionNormalColored pnt = new CustomVertex.PositionNormalColored();
					double vx = x * squareSize - offsetX;
					if(mapSpan != 0) // Do ortho projection
					{
						Vector2 latLon = this.SampleToLatLon(x, y, slices + 1, mapLat, mapLon, mapSpan, mapHeading);
						Vector2 proj = this.ProjectToPlane(latLon.X, latLon.Y, mapLat, mapLon, worldRadius);
						vx = proj.X;
						vy = proj.Y;
					}
					pnt.X = (float)vx;
					pnt.Y = (float)vy;
					pnt.Z = (float)alt * verticalFactor;
					pnt.Color = color.ToArgb();
					pnt.Normal = new Vector3(0f, 0f, 1f);
					arr.SetValue(pnt,vertIndex++);
				}
			}
			mesh.VertexBuffer.Unlock();

			// Set index buffer
			ranks[0]=indexCount;
			arr = mesh.LockIndexBuffer(typeof(short),LockFlags.None,ranks);
			int i=0;
			short bottomVertex = 0;
			short topVertex = 0;
			for(short y = 0; y < slices; y++)
			{
				topVertex = (short)((slices+1) * y);
				bottomVertex = (short)(topVertex + slices + 1);
				for(int x = 0; x < slices; x++)
				{
					arr.SetValue(bottomVertex,i++);
					arr.SetValue(topVertex,i++);
					arr.SetValue((short)(topVertex+1),i++);
					arr.SetValue(bottomVertex,i++);
					arr.SetValue((short)(topVertex+1),i++);
					arr.SetValue((short)(bottomVertex+1),i++);
					bottomVertex++;
					topVertex++;
				}
			}
			mesh.IndexBuffer.SetData(arr,0,LockFlags.None);

			return mesh;
		}

		/// <summary>
		/// Draws infoText string at the bottom of the screen
		/// </summary>
		protected void DrawInfoText()
		{
			infoText = "";
			if(verticalFactor != 1) infoText += "x" + verticalFactor.ToString() + " ";
			infoText += String.Format("Head {0:f2} Tilt {1:f2}", 90f - angle * 180 / Math.PI, angle2 * 180 / Math.PI);
			if(mapWidth != 0) 
			{
				// Terrain picking
				Vector2 DEMCoord;
				Vector3 mouseXY = ScreenToTerrain(mouse.X, mouse.Y);
				if(mouseXY != Vector3.Empty) 
				{
					Vector2 mouseGeo = ProjectToSphere(mouseXY.X, mouseXY.Y, mapLat, mapLon, worldRadius);
					infoText += String.Format("  Mouse Lat {0:f4} Lon {1:f4}", mouseGeo.X, mouseGeo.Y);
					DEMCoord = LatLonToSample(mouseGeo.X, mouseGeo.Y, DEM.Width, mapLat, mapLon, mapSpan, mapHeading);
					infoText += String.Format("  Alt {0:f1}", GetAlt(DEM, DEMCoord.X, DEMCoord.Y));
					this.Cursor = Cursors.Cross;
				}
				else
				{
					this.Cursor = Cursors.Default;
				}
			}
			Rectangle rect = new Rectangle(6, this.ClientSize.Height - 18, this.ClientSize.Width, 20);
			sprite.Begin(SpriteFlags.None);
			font.DrawText(sprite, infoText, rect, DrawTextFormat.Left, Color.Black);
			rect.X -= 1; rect.Y -= 1;
			font.DrawText(sprite, infoText, rect, DrawTextFormat.Left, Color.White);
			sprite.End();
		}

		/// <summary>
		/// Draws a small cross hairs for the user to pinpoint the exact lat/lon
		/// </summary>
		Line crossHairs;
		int crossHairColor = Color.GhostWhite.ToArgb();
		protected void DrawCrossHairs()
		{
			int crossHairSize = 10;

			if(this.crossHairs == null)
			{
				crossHairs = new Line(device);
			}

			Vector2[] vertical1 = new Vector2[2];
			Vector2[] horizontal1 = new Vector2[2];
			Vector2[] vertical2 = new Vector2[2];
			Vector2[] horizontal2 = new Vector2[2];

			horizontal1[0].X = this.ClientSize.Width / 2 - crossHairSize;
			horizontal1[0].Y = this.ClientSize.Height / 2;
			horizontal1[1].X = this.ClientSize.Width / 2 - 3;
			horizontal1[1].Y = this.ClientSize.Height / 2;
			horizontal2[0].X = this.ClientSize.Width / 2 + 3;
			horizontal2[0].Y = this.ClientSize.Height / 2;
			horizontal2[1].X = this.ClientSize.Width / 2 + crossHairSize;
			horizontal2[1].Y = this.ClientSize.Height / 2;

			vertical1[0].X = this.ClientSize.Width / 2;
			vertical1[0].Y = this.ClientSize.Height / 2 - crossHairSize;
			vertical1[1].X = this.ClientSize.Width / 2;
			vertical1[1].Y = this.ClientSize.Height / 2 - 3;
			vertical2[0].X = this.ClientSize.Width / 2;
			vertical2[0].Y = this.ClientSize.Height / 2  + 3;
			vertical2[1].X = this.ClientSize.Width / 2;
			vertical2[1].Y = this.ClientSize.Height / 2 + crossHairSize;

			crossHairs.Begin();
			crossHairs.Draw(horizontal1, crossHairColor);
			crossHairs.Draw(vertical1, crossHairColor);
			crossHairs.Draw(horizontal2, crossHairColor);
			crossHairs.Draw(vertical2, crossHairColor);
			crossHairs.End();
		}


		// Orthographic projection
		/// <summary>
		/// Project a lat/lon onto the plane tangent at lat0/lon0 at a given world radius
		/// Result x and y are is the plane coodinate system with origin at lat0/lon0
		/// in same unit as the world radius
		/// From http://en.wikipedia.org/wiki/Orthographic_projection_%28cartography%29
		/// </summary>
		/// <param name="lat">The latitude of the point to project - decimal degrees.</param>
		/// <param name="lon">The longitude of the point to project - decimal degrees.</param>
		/// <param name="lat0">The latitude of the plane contact point - decimal degrees.</param>
		/// <param name="lon0">The longitude of the plane contact point - decimal degrees.</param>
		/// <param name="radius">World radius - same unit as expected result.</param>
		public Vector2 ProjectToPlane(double lat, double lon, double lat0, double lon0, double radius)
		{
			double degToRad = Math.PI / 180.0;
			double latRad = lat * degToRad;
			double lonRad = lon * degToRad;
			double lat0Rad = lat0 * degToRad;
			double lon0Rad = lon0 * degToRad;
			// Check if not too far - other side of sphere
			double cosC = Math.Sin(lat0Rad) * Math.Sin(latRad) + Math.Cos(lat0Rad) * Math.Cos(latRad) * Math.Cos(lonRad - lon0Rad);
			if(cosC < 0) return Vector2.Empty;
			// Project
			Vector2 pnt = new Vector2();
			pnt.X = (float)(radius * Math.Cos(latRad) * Math.Sin(lonRad - lon0Rad));
			pnt.Y = -(float)(radius * (Math.Cos(lat0Rad) * Math.Sin(latRad) - Math.Sin(lat0Rad) * Math.Cos(latRad) * Math.Cos(lonRad - lon0Rad)));
			return pnt;
		}

		/// Inverse projection
		/// <summary>
		/// Project a x/y onto the sphere tangent at lat0/lon0 at a given world radius
		/// x/y in same unit as the world radius
		/// From http://en.wikipedia.org/wiki/Orthographic_projection_%28cartography%29
		/// </summary>
		/// <param name="x">The x of the point to project.</param>
		/// <param name="y">The y of the point to project.</param>
		/// <param name="lat0">The latitude of the plane contact point - decimal degrees.</param>
		/// <param name="lon0">The longitude of the plane contact point - decimal degrees.</param>
		/// <param name="radius">World radius.</param>
		public Vector2 ProjectToSphere(double x, double y, double lat0, double lon0, double radius)
		{
			y = -y;
			double degToRad = Math.PI / 180.0;
			double lat0Rad = lat0 * degToRad;
			double lon0Rad = lon0 * degToRad;
			double p = Math.Sqrt(x * x + y * y);
			double c = Math.Asin(p / radius);
			// Project
			Vector2 pnt = new Vector2();
			// Lat
			pnt.X = (float)lat0Rad;
			if(p != 0) pnt.X = (float)Math.Asin(Math.Cos(c) * Math.Sin(lat0Rad) + y * Math.Sin(c) * Math.Cos(lat0Rad) / p);
			pnt.X /= (float)degToRad;
			// Lon
			pnt.Y = (float)lon0Rad;
			if(p != 0) pnt.Y = (float)(lon0Rad + Math.Atan(x * Math.Sin(c) / (p * Math.Cos(lat0Rad) * Math.Cos(c) - y * Math.Sin(lat0Rad) * Math.Sin(c))));
            pnt.Y /= (float)degToRad;
			return pnt;
		}

		public Vector2 SampleToLatLon(double x, double y, int samples, double centerLat, double centerLon, double span, double heading)
		{
			double degToRad = Math.PI / 180.0;
			// lat and lon at start of first row - north-west
			double nwLon = centerLon - span * 1.4142 * Math.Cos((double)(45 - heading) * degToRad) / 2;
			double nwLat = centerLat + span * 1.4142 * Math.Sin((double)(45 - heading) * degToRad) / 2;
			// sample spacing 
			double stepSize = span / (samples - 1);
			// some trig shortcuts for later
			double ssin = stepSize * Math.Sin(heading * degToRad);
			double scos = stepSize * Math.Cos(heading * degToRad);
			Vector2 pnt = new Vector2();
			pnt.Y = (float)(nwLon + ssin * y + scos * x); // Longitude
			pnt.X = (float)(nwLat - scos * y + ssin * x); // Latitude
			return pnt;
		}

		public Vector2 LatLonToSample(double lat, double lon, int samples, double centerLat, double centerLon, double span, double heading)
		{
			double degToRad = Math.PI / 180.0;
			// lat and lon at start of first row - north-west
			double nwLon = centerLon - span * 1.4142 * Math.Cos((double)(45 - heading) * degToRad) / 2;
			double nwLat = centerLat + span * 1.4142 * Math.Sin((double)(45 - heading) * degToRad) / 2;
			// sample spacing 
			double stepSize = span / (samples - 1);
			// Compute cood
			double dLat = nwLat - lat;
			double dLon = lon - nwLon;
			double d = Math.Sqrt(dLat * dLat + dLon * dLon);
			double a = Math.Asin(dLon / d);
			double A = a - heading * degToRad;
			Vector2 pnt = new Vector2();
			pnt.X = (float)(d * Math.Sin(A) / stepSize);
			pnt.Y = (float)(d * Math.Cos(A) / stepSize);
			if(dLat < 0) pnt.Y = - pnt.Y;
			return pnt;
		}

		// Ray tracing from screen to x/y plane
		// http://local.wasp.uwa.edu.au/~pbourke/geometry/planeline/
		public Vector3 ScreenToPlane(int x, int y)
		{
			Vector3 p1 = new Vector3(x, y, device.Viewport.MinZ);
			p1.Unproject(device.Viewport, device.Transform.Projection, device.Transform.View, device.Transform.World);
			Vector3 p2 = new Vector3(x, y, device.Viewport.MaxZ);
			p2.Unproject(device.Viewport, device.Transform.Projection, device.Transform.View, device.Transform.World);
			Vector3 ray = Vector3.Empty;
			if(p1.Z - p2.Z != 0) 
			{
				double u = p1.Z / (p1.Z - p2.Z);
				if(u > 0) 
				{
					ray = p2 - p1;
					ray.Scale((float)u);
					ray = p1 + ray;
				}
			}
			return ray;
		}

		// Ray tracing from screen to terrain geometry
		// http://local.wasp.uwa.edu.au/~pbourke/geometry/planeline/
		public Vector3 ScreenToTerrain(int x, int y)
		{
			Vector3 p1 = new Vector3(x, y, device.Viewport.MinZ);
			p1.Unproject(device.Viewport, device.Transform.Projection, device.Transform.View, device.Transform.World);
			Vector3 p2 = new Vector3(x, y, device.Viewport.MaxZ);
			p2.Unproject(device.Viewport, device.Transform.Projection, device.Transform.View, device.Transform.World);
			if(p1.Z - p2.Z != 0) 
			{
				// Find x/y plane
				double u = p1.Z / (p1.Z - p2.Z);
				Vector3 sample = p2 - p1;
				if(u > 0 && p1.Z > mapMaxAlt * verticalFactor)
				{
					// x/y plane is forward - looking down
					double uTop = (p1.Z - mapMaxAlt * verticalFactor) / (p1.Z - p2.Z);
					sample.Scale((float)uTop);
					p1 = p1 + sample;		// Start sampling from max altitude
				}
				sample.Normalize();		// 1 meter sample
				sample.Scale(10);		// 10 meter sample
				float sampleLength = sample.Length();
				double rayLength = 0;
				double maxLength = mapWidth * 2;
				bool previousSampleInsideMap = false;
				Vector3 ray = p1;
				// Tracing
				while(rayLength < maxLength && ((u > 0 && ray.Z > mapMinAlt * verticalFactor) || (u < 0 && ray.Z < mapMaxAlt * verticalFactor)))
				{
					Vector2 latLon = ProjectToSphere(ray.X, ray.Y, mapLat, mapLon, worldRadius);
					Vector2 DEMCoord = LatLonToSample(latLon.X, latLon.Y, DEM.Width, mapLat, mapLon, mapSpan, mapHeading);
					float terrainAlt = GetAlt(DEM, DEMCoord.X, DEMCoord.Y) * verticalFactor;
					if(terrainAlt > ray.Z)
					{
						if(previousSampleInsideMap && terrainAlt - ray.Z < rayLength) 
						{
							ray.Z = terrainAlt;
							return ray;
						}
						else return Vector3.Empty;
					}
					if(DEMCoord.X >= 0 && DEMCoord.X <= DEM.Width - 1 && DEMCoord.Y >= 0 && DEMCoord.Y <= DEM.Height - 1) previousSampleInsideMap = true;
					ray += sample;
					rayLength += sampleLength;
				}
			}
			return Vector3.Empty;
		}


		// Altitude and texture mapping
		public void FindAltitudeExtremes(Bitmap DEM)
		{
			mapMaxAlt = int.MinValue;
			mapMinAlt = int.MaxValue;
			for (int x = 0; x < DEM.Width; x++)
			{
				for (int y = 0; y < DEM.Height; y++)
				{
					int alt = GetAlt(DEM, x, y);
					mapMaxAlt = alt > mapMaxAlt ? alt : mapMaxAlt;
					mapMinAlt = alt < mapMinAlt ? alt : mapMinAlt;
				}
			}
			if(mapMinAlt > 0) mapMinAlt = 0;
			if(mapMaxAlt < 1000 && dem16) mapMaxAlt = 1000;
		}

		// Get elevation from DEM at exact location (integer coord)
		public int GetAlt(Bitmap DEM, int x, int y)
		{
			int alt = 0;
			if(x >= 0 && x <= DEM.Width - 1 && y >= 0 && y <= DEM.Height - 1)
			{
				Color p = DEM.GetPixel(x, y);
				alt = (int)p.R; // Get altitude from red (8 bit)
				if(dem16) 
				{
					// Get 16bit altitude from g/b
					alt = (short)(p.B << 8) + (int)p.G;
					// Check for negative values
					if(alt > 32767) alt = 65536 - alt;
				}
			}
			return alt;
		}

		// Get averaged elevation from DEM at decimal location (float coord)
		public float GetAlt(Bitmap DEM, float x, float y)
		{
			float alt = 0f;
			if(x >= 0 && x <= DEM.Width - 1 && y >= 0 && y <= DEM.Height - 1)
			{
				int xNW = (int)Math.Floor(x);				// North-West corner
				int yNW = (int)Math.Floor(y);
				float xF = (float)x - xNW;					// x factor 0...1
				float yF = (float)y - yNW;					// y factor 0...1
				int altNW = GetAlt(DEM, xNW, yNW);			// Alt north-west
				int altNE = GetAlt(DEM, xNW + 1, yNW);		// Alt north-east
				int altSW = GetAlt(DEM, xNW, yNW + 1);		// Alt south-west
				int altSE = GetAlt(DEM, xNW + 1, yNW + 1);	// Alt south-east
				float altN = (float)altNW * (1f - xF) + (float)altNE * xF;	// North average
				float altS = (float)altSW * (1f - xF) + (float)altSE * xF;	// South average
				alt = altN * (1f - yF) +  altS * yF;	// North-South average
			}
			return alt;
		}

		public float GetTvFromAltitude(int alt, string fileName) 
		{
			float Tv =  1f - (float)alt / (float)mapMaxAlt;
			if(fileName.IndexOf("Water") != -1) 
			{
				Tv = 0.5f - ((float)alt / (float)Math.Max(Math.Abs(mapMaxAlt), Math.Abs(mapMinAlt)) / 2f);
			}
			return Tv;
		}

		// Compute map span from SRTM level if available in dem path
		public double GetSpanFromPath(string fileName)
		{
			// Span from SRTM level
			double span = 0;
			int level = 0;
			Regex exp = new Regex(@"\\[0-9][0-9]?\\", RegexOptions.IgnoreCase);
			Match m = exp.Match(fileName);
			if(m.Success) 
			{
				level = int.Parse(m.ToString().Substring(1, m.Length - 2));
				span = 20.0;	// SRTM level zero tile size
				if(level > 0) for(int i = 0; i < level; i++) span /= 2;
				// Lat/lon from SRTM row/col
				exp = new Regex(@"[0-9]{4}_[0-9]{4}", RegexOptions.IgnoreCase);
				m = exp.Match(fileName);
				if(m.Success) 
				{
					int row = int.Parse(m.ToString().Split('_')[0]);
					int col = int.Parse(m.ToString().Split('_')[1]);
					mapLat = -90 + span * row + span / 2;
					mapLon = -180 + span * col + span / 2;
				}
			}
			return span;
		}

		// Converts a .bil elevation data file into a Bitmap object
		private Bitmap BitmapFromBil(string bilFile)
		{
			int width = 150; // default size
			int height = 150;
			//Bitmap DEM;
			using( Stream s = File.OpenRead(bilFile))
			{
				// Find out dem size
				FileInfo demFile = new FileInfo(bilFile);
				long length = demFile.Length;
				if(length != 0) 
				{
					width = height = (int)Math.Sqrt(length / 2);
					if(width * height * 2 != length) throw new ApplicationException(".bil file size not double of a square (eg. 150x150x2)");
				}
				DEM = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
				byte[] tfBuffer = new byte[width * height * 2];
				if (s.Read(tfBuffer,0,tfBuffer.Length) < tfBuffer.Length)
					throw new IOException(string.Format("End of file error while reading file '{0}'.", bilFile) );

				int offset = 0;
				for(int y = 0; y < height; y++)
				{
					for(int x = 0; x < width; x++) 
					{
						// 16 bit values
						int low = tfBuffer[offset++];
						int hi =  tfBuffer[offset];

                        //added by JK
                        //if (hi == 0)
                        //{
                        //    hi = 1;
                        //    low = 0;
                        //}

						int hi2 =  (short)(tfBuffer[offset++] << 8);
						// Scale down to 0..255
						int alt = (int)((float)(hi2 + low) * 255f / 9000f);
						if (alt < 0) alt = 0;
						if (alt > 255) alt = 255;
                        if (alt > 0)
                        {
                            alt = alt * 1;
                        }
                        if (hi2 > 0 || hi > 0)
                        {
                            int orig1 = hi2 + low;
                            double orig2 = (double)(alt) * (9000f / 255f);
                        }
                        if (hi2 < 0 || hi < 0)
                        {
                            int orig3 = hi2 + low;
                            double orig4 = (double)(alt) * (9000f / 255f);
                        }
						// Store altitude in red, and original 16 bit value in g and b
						DEM.SetPixel(x, y, Color.FromArgb(0xff, alt, low, hi));
					}
				}
			}
			dem16 = true;
			return DEM;
		}

		// Create the light map for current light position (heading / elevation)
		private Texture LightMap(Microsoft.DirectX.Direct3D.Device device, Bitmap DEM, int precision) 
		{
			Bitmap b;
			int width = DEM.Width * precision;
			int height = DEM.Height * precision;
			b = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
			for(int x = 0; x < width; x++)
			{
				for(int y = 0; y < height; y++)
				{
					float xP = (float)x / precision;
					float yP = (float)y / precision;
					Color p = GetShadowAt(DEM, xP, yP, precision);
					b.SetPixel(x, y, p);
				}
			}
			b = BlurBitmap(b);
			b = BlurBitmap(b);
			Texture t = new Texture( device, b, 0, Pool.Managed );
			b.Dispose();
			return t;
		}

		// Follow line of sight to the light and check for terrain occlusion
		public Color GetShadowAt(Bitmap DEM, float x, float y, int precision)
		{
			Color color = Color.White;
			if(lightElevation > 85f * 180f / Math.PI) return color; // light almost at zenith
			int squareSize = 1;
			if(mapWidth != 0) squareSize = (int)(mapWidth / (DEM.Width - 1));
			float stepSize = 5f / precision;
			float stepX = stepSize * (float)Math.Cos(lightHeading);
			float stepY = stepSize * (float)Math.Sin(lightHeading);
			float stepZ = stepSize * (float)Math.Tan(lightElevation) * (float)squareSize;
			float alt = (float)GetAlt(DEM, x, y) * verticalFactor;
			while(x >=0 && x < DEM.Width && y >= 0 && y < DEM.Height && alt <= mapMaxAlt * verticalFactor && alt >= mapMinAlt * verticalFactor)
			{
				x += stepX;
				y += stepY;
				alt += stepZ;
				float terrainAlt = (float)GetAlt(DEM, x, y) * verticalFactor;
				if(terrainAlt > alt) 
				{
					// Terrain occlusion
					color = ambientColor;
					return color;
				}
				
			}
			return color;
		}

		// Blur a bitmap
		private Bitmap BlurBitmap(Bitmap b1) 
		{
			Bitmap b2 = new Bitmap(b1.Width, b1.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
			for(int x = 0; x < b1.Width; x++)
			{
				for(int y = 0; y < b1.Height; y++)
				{
					Color p = b1.GetPixel(x,y);
					Color pW = (x - 1 >= 0) ? b1.GetPixel(x - 1, y) : p;
					Color pE = (x + 1 < b1.Width) ? b1.GetPixel(x + 1, y) : p;
					Color pN = (y - 1 >= 0) ? b1.GetPixel(x, y - 1) : p;
					Color pS = (y + 1 < b1.Height) ? b1.GetPixel(x, y + 1) : p;
					int R = (int)((float)p.R * 0.4f + (float)(pW.R + pE.R + pN.R + pS.R) * 0.6f / 4f);
					int G = (int)((float)p.G * 0.4f + (float)(pW.G + pE.G + pN.G + pS.G) * 0.6f / 4f);
					int B = (int)((float)p.B * 0.4f + (float)(pW.B + pE.B + pN.B + pS.B) * 0.6f / 4f);
					b2.SetPixel(x, y, Color.FromArgb(R, G, B));
				}
			}
			b1.Dispose();
			return b2;
		}

		// Create a colorized version of the DEM
		private Texture TextureFromDem(Microsoft.DirectX.Direct3D.Device device, Bitmap DEM) 
		{
			// Open elevation bitmap
			Bitmap tex;
			Bitmap colors = null;
			tex = new Bitmap(DEM.Width, DEM.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
			if(textureFileName != "") colors = new Bitmap(textureFileName);
			for(int x = 0; x < DEM.Width; x++)
			{
				for(int y = 0; y < DEM.Height; y++)
				{
					Color p = DEM.GetPixel(x,y);
					float v = (float)p.R / 255f;	// Scale Red to range 0..1
					p = (colors == null) ? HueToRGB(v) : colors.GetPixel(0, (int)((colors.Height - 1)  - v * (float)(colors.Height - 1)));
					tex.SetPixel(x, y, p);
				}
			}
			Texture t = new Texture( device, tex, 0, Pool.Managed );
			DEM.Dispose();
			tex.Dispose();
			return t;
		}

		// Internal color scheme
		private Color HueToRGB(float hue) 
		{
			float red = 0;
			float green = 0;
			float blue = 0;
			hue = hue * 6;
			if (hue <= 1) 
			{
				red=1; green=hue; 
			} 
			else 
			{
				if (hue <= 2) 
				{
					red=1-(hue-1); green=1;
				}
				else 
				{
					if (hue <= 3) 
					{
						blue=hue-2; green=1; 
					} 
					else 
					{
						if (hue <= 4) 
						{
							blue=1; green=1-(hue-3); 
						} 
						else 
						{
							if (hue <= 5) 
							{
								blue=1; red=hue-4; 
							} 
							else 
							{
								blue=1-(hue-5); red=1; 
							}
						}
					} 
				} 
			}
			return Color.FromArgb(0xff, (byte)(red * 255), (byte)(green * 255), (byte)(blue * 255));
		}


		/// <summary>
		/// Converts position in spherical coordinates (lat/lon/altitude) to cartesian (XYZ) coordinates.
		/// </summary>
		/// <param name="latitude">Latitude in decimal degrees</param>
		/// <param name="longitude">Longitude in decimal degrees</param>
		/// <param name="radius">Radius (OBS: not altitude)</param>
		/// <returns>Coordinates converted to cartesian (XYZ)</returns>
		public static Vector3 SphericalToCartesian(
			double latitude,
			double longitude,
			double radius
			)
		{
			latitude *= System.Math.PI / 180.0f;
			longitude *= System.Math.PI /180.0f;

			double radCosLat = radius * Math.Cos(latitude);

			return new Vector3(
				(float)(radCosLat * Math.Cos(longitude)),
				(float)(radCosLat * Math.Sin(longitude)),
				(float)(radius * Math.Sin(latitude)) );
		}

		protected override void Dispose (bool disposing)
		{
			DisposeMap();
			if (disposing)
			{
				if (components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose(disposing);
		}
 
		private void InitializeComponent()
		{
            this.components = new System.ComponentModel.Container();
            System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Viewer));
            this.mainMenu = new System.Windows.Forms.MainMenu(this.components);
            this.menuItemFile = new System.Windows.Forms.MenuItem();
            this.menuItemLoadMaps = new System.Windows.Forms.MenuItem();
            this.menuItemQuit = new System.Windows.Forms.MenuItem();
            this.menuItemMaps = new System.Windows.Forms.MenuItem();
            this.menuItemView = new System.Windows.Forms.MenuItem();
            this.menuItemShowLight = new System.Windows.Forms.MenuItem();
            this.menuItemShowWater = new System.Windows.Forms.MenuItem();
            this.menuItemShowFog = new System.Windows.Forms.MenuItem();
            this.menuItemShowInfo = new System.Windows.Forms.MenuItem();
            this.menuItemShowCrosshair = new System.Windows.Forms.MenuItem();
            this.menuItemShowWorldMap = new System.Windows.Forms.MenuItem();
            this.menuItemShowSection = new System.Windows.Forms.MenuItem();
            this.menuItemShowSpin = new System.Windows.Forms.MenuItem();
            this.menuItemVerticalFactor = new System.Windows.Forms.MenuItem();
            this.menuItemLightMap = new System.Windows.Forms.MenuItem();
            this.menuItemTextures = new System.Windows.Forms.MenuItem();
            this.menuItemSides = new System.Windows.Forms.MenuItem();
            this.menuItemSky = new System.Windows.Forms.MenuItem();
            this.panel1 = new System.Windows.Forms.Panel();
            this.lblW = new System.Windows.Forms.Label();
            this.lblE = new System.Windows.Forms.Label();
            this.label1 = new System.Windows.Forms.Label();
            this.lblNorth = new System.Windows.Forms.Label();
            this.btnReset2 = new System.Windows.Forms.Button();
            this.btnReset = new System.Windows.Forms.Button();
            this.cmbVerticalFactor = new TerrainViewer.NoWheelComboBox();
            this.lblOverviewMap = new System.Windows.Forms.Label();
            this.lblVerticalFactor = new System.Windows.Forms.Label();
            this.btnZoomOut = new System.Windows.Forms.Button();
            this.btnZoomIn = new System.Windows.Forms.Button();
            this.btnDown = new System.Windows.Forms.Button();
            this.btnUp = new System.Windows.Forms.Button();
            this.btnRight = new System.Windows.Forms.Button();
            this.btnLeft = new System.Windows.Forms.Button();
            this.toolTip1 = new System.Windows.Forms.ToolTip(this.components);
            this.panel1.SuspendLayout();
            this.SuspendLayout();
            // 
            // mainMenu
            // 
            this.mainMenu.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
            this.menuItemFile,
            this.menuItemMaps,
            this.menuItemView,
            this.menuItemTextures,
            this.menuItemSides,
            this.menuItemSky});
            // 
            // menuItemFile
            // 
            this.menuItemFile.Index = 0;
            this.menuItemFile.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
            this.menuItemLoadMaps,
            this.menuItemQuit});
            this.menuItemFile.Text = "&File";
            this.menuItemFile.Visible = false;
            // 
            // menuItemLoadMaps
            // 
            this.menuItemLoadMaps.Index = 0;
            this.menuItemLoadMaps.Text = "&Open maps...";
            this.menuItemLoadMaps.Click += new System.EventHandler(this.menuItemLoadMaps_Click);
            // 
            // menuItemQuit
            // 
            this.menuItemQuit.Index = 1;
            this.menuItemQuit.Text = "E&xit\tAlt+F4";
            this.menuItemQuit.Click += new System.EventHandler(this.menuItemQuit_Click);
            // 
            // menuItemMaps
            // 
            this.menuItemMaps.Index = 1;
            this.menuItemMaps.Text = "&Maps";
            this.menuItemMaps.Visible = false;
            // 
            // menuItemView
            // 
            this.menuItemView.Index = 2;
            this.menuItemView.MenuItems.AddRange(new System.Windows.Forms.MenuItem[] {
            this.menuItemShowLight,
            this.menuItemShowWater,
            this.menuItemShowFog,
            this.menuItemShowInfo,
            this.menuItemShowCrosshair,
            this.menuItemShowWorldMap,
            this.menuItemShowSection,
            this.menuItemShowSpin,
            this.menuItemVerticalFactor,
            this.menuItemLightMap});
            this.menuItemView.Text = "&View";
            // 
            // menuItemShowLight
            // 
            this.menuItemShowLight.Index = 0;
            this.menuItemShowLight.Text = "Shading\tL / Shift+L";
            this.menuItemShowLight.Click += new System.EventHandler(this.menuItemShowLight_Click);
            // 
            // menuItemShowWater
            // 
            this.menuItemShowWater.Index = 1;
            this.menuItemShowWater.Text = "Water";
            this.menuItemShowWater.Visible = false;
            this.menuItemShowWater.Click += new System.EventHandler(this.menuItemShowWater_Click);
            // 
            // menuItemShowFog
            // 
            this.menuItemShowFog.Index = 2;
            this.menuItemShowFog.Text = "Fog\tF / Shift+F";
            this.menuItemShowFog.Visible = false;
            this.menuItemShowFog.Click += new System.EventHandler(this.menuItemShowFog_Click);
            // 
            // menuItemShowInfo
            // 
            this.menuItemShowInfo.Index = 3;
            this.menuItemShowInfo.Text = "Info";
            this.menuItemShowInfo.Click += new System.EventHandler(this.menuItemShowInfo_Click);
            // 
            // menuItemShowCrosshair
            // 
            this.menuItemShowCrosshair.Index = 4;
            this.menuItemShowCrosshair.Text = "Crosshair";
            this.menuItemShowCrosshair.Click += new System.EventHandler(this.menuItemShowCrosshair_Click);
            // 
            // menuItemShowWorldMap
            // 
            this.menuItemShowWorldMap.Index = 5;
            this.menuItemShowWorldMap.Text = "World map";
            this.menuItemShowWorldMap.Visible = false;
            this.menuItemShowWorldMap.Click += new System.EventHandler(this.menuItemShowWorldMap_Click);
            // 
            // menuItemShowSection
            // 
            this.menuItemShowSection.Index = 6;
            this.menuItemShowSection.Text = "Section profile\tS / Shift+S";
            this.menuItemShowSection.Click += new System.EventHandler(this.menuItemShowSection_Click);
            // 
            // menuItemShowSpin
            // 
            this.menuItemShowSpin.Index = 7;
            this.menuItemShowSpin.Text = "Slow spin\tEnter / Space";
            this.menuItemShowSpin.Click += new System.EventHandler(this.menuItemShowSpin_Click);
            // 
            // menuItemVerticalFactor
            // 
            this.menuItemVerticalFactor.Index = 8;
            this.menuItemVerticalFactor.Text = "Vertical factor";
            this.menuItemVerticalFactor.Visible = false;
            // 
            // menuItemLightMap
            // 
            this.menuItemLightMap.Index = 9;
            this.menuItemLightMap.Text = "Compute shadows\tCtrl+L / Ctrl+Shift+L";
            this.menuItemLightMap.Visible = false;
            this.menuItemLightMap.Click += new System.EventHandler(this.menuItemLightMap_Click);
            // 
            // menuItemTextures
            // 
            this.menuItemTextures.Index = 3;
            this.menuItemTextures.Text = "&Textures";
            this.menuItemTextures.Visible = false;
            // 
            // menuItemSides
            // 
            this.menuItemSides.Index = 4;
            this.menuItemSides.Text = "&Sides";
            this.menuItemSides.Visible = false;
            // 
            // menuItemSky
            // 
            this.menuItemSky.Index = 5;
            this.menuItemSky.Text = "&Sky";
            this.menuItemSky.Visible = false;
            // 
            // panel1
            // 
            this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
            | System.Windows.Forms.AnchorStyles.Right)));
            this.panel1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch;
            this.panel1.Controls.Add(this.lblW);
            this.panel1.Controls.Add(this.lblE);
            this.panel1.Controls.Add(this.label1);
            this.panel1.Controls.Add(this.lblNorth);
            this.panel1.Controls.Add(this.btnReset2);
            this.panel1.Controls.Add(this.btnReset);
            this.panel1.Controls.Add(this.cmbVerticalFactor);
            this.panel1.Controls.Add(this.lblOverviewMap);
            this.panel1.Controls.Add(this.lblVerticalFactor);
            this.panel1.Controls.Add(this.btnZoomOut);
            this.panel1.Controls.Add(this.btnZoomIn);
            this.panel1.Controls.Add(this.btnDown);
            this.panel1.Controls.Add(this.btnUp);
            this.panel1.Controls.Add(this.btnRight);
            this.panel1.Controls.Add(this.btnLeft);
            this.panel1.Location = new System.Drawing.Point(845, 2);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(148, 546);
            this.panel1.TabIndex = 0;
            // 
            // lblW
            // 
            this.lblW.AutoSize = true;
            this.lblW.BackColor = System.Drawing.Color.Transparent;
            this.lblW.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.lblW.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
            this.lblW.Location = new System.Drawing.Point(127, 322);
            this.lblW.Name = "lblW";
            this.lblW.Size = new System.Drawing.Size(16, 15);
            this.lblW.TabIndex = 14;
            this.lblW.Text = "W";
            // 
            // lblE
            // 
            this.lblE.AutoSize = true;
            this.lblE.BackColor = System.Drawing.Color.Transparent;
            this.lblE.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.lblE.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
            this.lblE.Location = new System.Drawing.Point(5, 320);
            this.lblE.Name = "lblE";
            this.lblE.Size = new System.Drawing.Size(13, 15);
            this.lblE.TabIndex = 13;
            this.lblE.Text = "E";
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.label1.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
            this.label1.Location = new System.Drawing.Point(65, 385);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(13, 15);
            this.label1.TabIndex = 12;
            this.label1.Text = "S";
            // 
            // lblNorth
            // 
            this.lblNorth.AutoSize = true;
            this.lblNorth.Font = new System.Drawing.Font("Arial Narrow", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.lblNorth.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(192)))), ((int)(((byte)(0)))), ((int)(((byte)(0)))));
            this.lblNorth.Location = new System.Drawing.Point(65, 261);
            this.lblNorth.Name = "lblNorth";
            this.lblNorth.Size = new System.Drawing.Size(14, 15);
            this.lblNorth.TabIndex = 11;
            this.lblNorth.Text = "N";
            // 
            // btnReset2
            // 
            this.btnReset2.Font = new System.Drawing.Font("Arial", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.btnReset2.Location = new System.Drawing.Point(24, 406);
            this.btnReset2.Name = "btnReset2";
            this.btnReset2.Size = new System.Drawing.Size(99, 34);
            this.btnReset2.TabIndex = 10;
            this.btnReset2.Text = "Reset View";
            this.toolTip1.SetToolTip(this.btnReset2, "Reset View to Default");
            this.btnReset2.UseVisualStyleBackColor = true;
            this.btnReset2.Click += new System.EventHandler(this.btnReset_Click);
            // 
            // btnReset
            // 
            this.btnReset.Image = global::TerrainViewer.Images.circle;
            this.btnReset.Location = new System.Drawing.Point(54, 48);
            this.btnReset.Name = "btnReset";
            this.btnReset.Size = new System.Drawing.Size(36, 36);
            this.btnReset.TabIndex = 9;
            this.toolTip1.SetToolTip(this.btnReset, "Reset View");
            this.btnReset.UseVisualStyleBackColor = true;
            this.btnReset.Click += new System.EventHandler(this.btnReset_Click);
            // 
            // cmbVerticalFactor
            // 
            this.cmbVerticalFactor.AutoCompleteCustomSource.AddRange(new string[] {
            "1",
            "2",
            "5",
            "10",
            "20"});
            this.cmbVerticalFactor.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
            this.cmbVerticalFactor.FormattingEnabled = true;
            this.cmbVerticalFactor.Location = new System.Drawing.Point(24, 229);
            this.cmbVerticalFactor.Name = "cmbVerticalFactor";
            this.cmbVerticalFactor.Size = new System.Drawing.Size(99, 21);
            this.cmbVerticalFactor.TabIndex = 8;
            this.toolTip1.SetToolTip(this.cmbVerticalFactor, "Change depth multiplication factor");
            this.cmbVerticalFactor.SelectedIndexChanged += new System.EventHandler(this.cmbVerticalFactor_SelectedIndexChanged);
            // 
            // lblOverviewMap
            // 
            this.lblOverviewMap.BackColor = System.Drawing.Color.White;
            this.lblOverviewMap.Location = new System.Drawing.Point(21, 277);
            this.lblOverviewMap.Name = "lblOverviewMap";
            this.lblOverviewMap.Size = new System.Drawing.Size(105, 105);
            this.lblOverviewMap.TabIndex = 7;
            // 
            // lblVerticalFactor
            // 
            this.lblVerticalFactor.AutoSize = true;
            this.lblVerticalFactor.Font = new System.Drawing.Font("Arial", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
            this.lblVerticalFactor.Location = new System.Drawing.Point(26, 211);
            this.lblVerticalFactor.Name = "lblVerticalFactor";
            this.lblVerticalFactor.Size = new System.Drawing.Size(102, 15);
            this.lblVerticalFactor.TabIndex = 6;
            this.lblVerticalFactor.Text = "Multiply elevation:";
            // 
            // btnZoomOut
            // 
            this.btnZoomOut.BackColor = System.Drawing.Color.White;
            this.btnZoomOut.Image = global::TerrainViewer.Images.zoomout3D;
            this.btnZoomOut.Location = new System.Drawing.Point(74, 144);
            this.btnZoomOut.Name = "btnZoomOut";
            this.btnZoomOut.Size = new System.Drawing.Size(49, 44);
            this.btnZoomOut.TabIndex = 5;
            this.btnZoomOut.UseVisualStyleBackColor = false;
            // 
            // btnZoomIn
            // 
            this.btnZoomIn.BackColor = System.Drawing.Color.White;
            this.btnZoomIn.Image = global::TerrainViewer.Images.zoomin3D;
            this.btnZoomIn.Location = new System.Drawing.Point(16, 144);
            this.btnZoomIn.Name = "btnZoomIn";
            this.btnZoomIn.Size = new System.Drawing.Size(49, 44);
            this.btnZoomIn.TabIndex = 4;
            this.btnZoomIn.UseVisualStyleBackColor = false;
            // 
            // btnDown
            // 
            this.btnDown.Image = ((System.Drawing.Image)(resources.GetObject("btnDown.Image")));
            this.btnDown.Location = new System.Drawing.Point(55, 87);
            this.btnDown.Name = "btnDown";
            this.btnDown.Size = new System.Drawing.Size(36, 36);
            this.btnDown.TabIndex = 3;
            this.toolTip1.SetToolTip(this.btnDown, "Rotate Down");
            this.btnDown.UseVisualStyleBackColor = true;
            // 
            // btnUp
            // 
            this.btnUp.Image = ((System.Drawing.Image)(resources.GetObject("btnUp.Image")));
            this.btnUp.Location = new System.Drawing.Point(54, 9);
            this.btnUp.Name = "btnUp";
            this.btnUp.Size = new System.Drawing.Size(36, 36);
            this.btnUp.TabIndex = 2;
            this.toolTip1.SetToolTip(this.btnUp, "Rotate Up");
            this.btnUp.UseVisualStyleBackColor = true;
            // 
            // btnRight
            // 
            this.btnRight.Image = ((System.Drawing.Image)(resources.GetObject("btnRight.Image")));
            this.btnRight.Location = new System.Drawing.Point(93, 48);
            this.btnRight.Name = "btnRight";
            this.btnRight.Size = new System.Drawing.Size(36, 36);
            this.btnRight.TabIndex = 1;
            this.toolTip1.SetToolTip(this.btnRight, "Rotate Right");
            this.btnRight.UseVisualStyleBackColor = true;
            // 
            // btnLeft
            // 
            this.btnLeft.Image = ((System.Drawing.Image)(resources.GetObject("btnLeft.Image")));
            this.btnLeft.Location = new System.Drawing.Point(15, 48);
            this.btnLeft.Name = "btnLeft";
            this.btnLeft.Size = new System.Drawing.Size(36, 36);
            this.btnLeft.TabIndex = 0;
            this.toolTip1.SetToolTip(this.btnLeft, "Rotate Left");
            this.btnLeft.UseVisualStyleBackColor = true;
            // 
            // Viewer
            // 
            this.ClientSize = new System.Drawing.Size(990, 547);
            this.Controls.Add(this.panel1);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
            this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
            this.MaximizeBox = false;
            this.Menu = this.mainMenu;
            this.MinimizeBox = false;
            this.Name = "Viewer";
            this.Text = "terrain viewer 1.0";
            this.panel1.ResumeLayout(false);
            this.panel1.PerformLayout();
            this.ResumeLayout(false);

		}

		// File drop handling
		private  void  OnDragEnter(object sender, System.Windows.Forms.DragEventArgs e)
		{
			e.Effect = DragDropEffects.Copy;  // set the cursor to show a drop copy
		}

		private  void  OnDragDrop(object sender, System.Windows.Forms.DragEventArgs e)
		{
			string theFile;
			try
			{
				// check to make sure the dropped item is of type FileDrop
				if (e.Data.GetDataPresent(DataFormats.FileDrop))
				{
					object filename = e.Data.GetData(DataFormats.FileDrop);
					theFile = (string)((System.Array)filename).GetValue(0);
					// Create map from file
					// MessageBox.Show("Dropped file : " + theFile);
					string ext = Path.GetExtension(theFile);
					string sky = skyFileName == null ? "" : skyFileName;
					string tex = textureFileName == null ? "" : textureFileName;
					if (!tex.StartsWith("colors")) tex = "colors/Geo_Water_1.png";
					switch(ext) 
					{
						case ".jpg" :
							break;
						case ".png" :		// Load terrain from 8bit .png
							DisposeMap();
							terrainFileName = theFile;
							skyFileName = sky;
							textureFileName = tex;
							mapName = terrainFileName;
							mapSpan = 0;
							mapWidth = 0;
							dem16 = false;
							verticalFactor = 1.0f;
							LoadMap();
							break;
						case ".bil" :		// Load terrain from 16bit SRTM binary .bil
							DisposeMap();
							terrainFileName = theFile;
							skyFileName = sky;
							textureFileName = tex;
							mapName = terrainFileName;
							mapSpan = 0;
							mapWidth = 0;
							dem16 = true;
							verticalFactor = 1.0f;
							LoadMap();
							break;
						case ".xml" :		// Load map list from .xml
							DisposeMap();
							mapListFileName = theFile;
							InitializeMapList();		// Create Map menu from .xml
							MapMenuSelectMap(0);		// Load first map
							break;
					}
				}
			}
			catch (Exception ex)
			{
				//MessageBox.Show(ex.Message.ToString());
			}
		}

		// Load .xml map list
		private void menuItemLoadMaps_Click(object sender, System.EventArgs e)
		{
			// File dialog
			string filePath = FileOpenDialog("Open a map list", "Extensible Markup Language (*.xml)|*.xml");
			if(filePath != "") 
			{
				DisposeMap();
				mapListFileName = filePath;
				try 
				{
					InitializeMapList();		// Create Map menu from .xml
					if(menuItemMaps.MenuItems.Count > 0)
					{
						MapMenuSelectMap(0);		// Load first map
						MenuEnabled(true);
					}
					else MenuEnabled(false);
				}
				catch
				{
					MenuEnabled(false);
				}
			}
		}

		
		private string FileOpenDialog(string title, string filter)
		{
			OpenFileDialog dlg = new OpenFileDialog();
			dlg.Title = title;
			dlg.Filter = filter;
			//dlg.RestoreDirectory = true;
			if (dlg.ShowDialog() != DialogResult.OK)
				return "";

			// FileDialog fails to add extension when the filename contains dots.
			string ext = "*" + Path.GetExtension(dlg.FileName).ToLower(CultureInfo.InvariantCulture);
			if(dlg.Filter.IndexOf(ext)<0)
			{
				// Extension missing, add it
				ext = dlg.Filter.Split('|')[ dlg.FilterIndex * 2 - 1 ].Substring(1);
				dlg.FileName += ext;
			}

			return dlg.FileName;
		}


		// Show light/shading menu
		private void menuItemShowLight_Click(object sender, System.EventArgs e)
		{
			showLight = !showLight;
			MenuItem mi = (MenuItem)sender;
			mi.Checked = showLight;
			redraw = true;
		}

		// Show water menu
		private void menuItemShowWater_Click(object sender, System.EventArgs e)
		{
			showWater = !showWater;
			MenuItem mi = (MenuItem)sender;
			mi.Checked = showWater;
			redraw = true;
		}

		// Show fog menu
		private void menuItemShowFog_Click(object sender, System.EventArgs e)
		{
			showFog = !showFog;
			MenuItem mi = (MenuItem)sender;
			mi.Checked = showFog;
			redraw = true;
		}

		// Show info menu
		private void menuItemShowInfo_Click(object sender, System.EventArgs e)
		{
			showInfo = !showInfo;
			MenuItem mi = (MenuItem)sender;
			mi.Checked = showInfo;
			if(!showInfo) this.Cursor = Cursors.Default;
			redraw = true;
		}

		// Show crosshair menu
		private void menuItemShowCrosshair_Click(object sender, System.EventArgs e)
		{
			showCrosshair = !showCrosshair;
			MenuItem mi = (MenuItem)sender;
			mi.Checked = showCrosshair;
			redraw = true;
		}

		// Show world map menu
		private void menuItemShowWorldMap_Click(object sender, System.EventArgs e)
		{
			showWorldMap = !showWorldMap;
			MenuItem mi = (MenuItem)sender;
			mi.Checked = showWorldMap;
			redraw = true;
		}

		// Slow spin menu
		private void menuItemShowSpin_Click(object sender, System.EventArgs e)
		{
			spin = !spin;
			MenuItem mi = (MenuItem)sender;
			mi.Checked = spin;
			redraw = true;
		}

		// Vertical factor menu
		private void menuItemVerticalFactor_Click(object sender, System.EventArgs e)
		{
			MenuItem mi = (MenuItem)sender;
			verticalFactor = float.Parse(mi.Text.Substring(1), System.Globalization.CultureInfo.InvariantCulture);
			MenuClearCheck(menuItemVerticalFactor);
			mi.Checked = true;
			this.Cursor = Cursors.WaitCursor;
			// Rebuilt terrain mesh
			DisposeTerrainMesh();
			BuildTerrainMesh();
			if(sidesMesh != null) sidesMesh.Dispose();
			sidesMesh = TerrainSides(device, DEM);
			this.Cursor = Cursors.Default;
			redraw = true;
		}

		// Compute shadows / light map menu
		private void menuItemLightMap_Click(object sender, System.EventArgs e)
		{
			if(textureFileName.IndexOf("colors") == -1) // only on textured maps
			{
				if(lightMapTexture != null) lightMapTexture.Dispose();
				lightMapTexture = null;
				this.Cursor = Cursors.WaitCursor;
				lightMapTexture = LightMap(device, DEM, 1);
				this.Cursor = Cursors.Default;
				redraw = true;
			}
		}

		// Compute shadows / light map menu
		private void menuItemShowSection_Click(object sender, System.EventArgs e)
		{
			MenuItem mi = (MenuItem)sender;
			showSection = !showSection;
			if(sectionMesh != null) sectionMesh.Dispose();
			sectionMesh = null;
			showTransparentTerrain = false;
			if(showSection) 
			{
				sectionMesh = TerrainSection(device, DEM);
				showTransparentTerrain = true;
			}
			mi.Checked = showSection;
			redraw = true;
		}

		// Quit
		private void menuItemQuit_Click(object sender, System.EventArgs e)
		{
			Close();
		}

		/// <summary>
		/// Creates a font.
		/// </summary>
		public Microsoft.DirectX.Direct3D.Font CreateFont( string familyName, float emSize, System.Drawing.FontStyle style )
		{
			try
			{
				FontDescription description = new FontDescription();
				description.FaceName = familyName;
				description.Height = (int)(1.9 * emSize);
				description.Quality = FontQuality.ClearTypeNatural;
				if(style != System.Drawing.FontStyle.Regular)
				{
					if((style & System.Drawing.FontStyle.Italic) != 0) description.IsItalic = true;
					if((style & System.Drawing.FontStyle.Bold) != 0) description.Weight = FontWeight.Heavy;
					description.Quality = FontQuality.AntiAliased;
				}
				return new Microsoft.DirectX.Direct3D.Font(device, description);
			}
			catch
			{
				return null;
			}
        }

        //*****************************************************************************
        #region ------------ Navigation Panel ------------------------------------------
        //*****************************************************************************
        //*****************************************************************************

        private void InitializeNavigationButtons()
        {
            if (navigationTimer == null)
            {
                navigationTimer = new Timer();
                navigationTimer.Interval = 50;
            }
            
            btnLeft.MouseDown += new MouseEventHandler(NavigationMouseDown);
            btnRight.MouseDown += new MouseEventHandler(NavigationMouseDown);
            btnUp.MouseDown += new MouseEventHandler(NavigationMouseDown);
            btnDown.MouseDown += new MouseEventHandler(NavigationMouseDown);
            btnZoomIn.MouseDown += new MouseEventHandler(NavigationMouseDown);
            btnZoomOut.MouseDown += new MouseEventHandler(NavigationMouseDown);

            btnLeft.MouseUp += new MouseEventHandler(NavigationMouseUp);
            btnRight.MouseUp += new MouseEventHandler(NavigationMouseUp);
            btnUp.MouseUp += new MouseEventHandler(NavigationMouseUp);
            btnDown.MouseUp += new MouseEventHandler(NavigationMouseUp);
            btnZoomIn.MouseUp += new MouseEventHandler(NavigationMouseUp);
            btnZoomOut.MouseUp += new MouseEventHandler(NavigationMouseUp);

            navigationTimer.Tick += new EventHandler(navigationTimer_Tick);
        }

        /// <summary>
        /// Occurs when a navigation button is pressed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void NavigationMouseDown(object sender, MouseEventArgs e)
        {
            if (navigationTimer == null)
            {
                return;
            }

            System.Windows.Forms.Button senderButton = null; 
            senderButton = sender as System.Windows.Forms.Button;

            if (senderButton == null)
            {
                return;
            }
            
            switch (senderButton.Name)
            {
                case "btnLeft":
                    navigationType = Navigation.Left;
                    break;
                case "btnRight":
                    navigationType = Navigation.Right;
                    break;
                case "btnUp":
                    navigationType = Navigation.Up;
                    break;
                case "btnDown":
                    navigationType = Navigation.Down;
                    break;
                case "btnZoomIn":
                    navigationType = Navigation.ZoomIn;
                    break;
                case "btnZoomOut":
                    navigationType = Navigation.ZoomOut;
                    break;
                default:
                    navigationType = Navigation.None;
                    break;
            }

            //if (navigationTimer.Enabled == true)
            //{
            //    navigationTimer.Stop();
            //}

            // starting the timer will start to continuously move the map
            if (navigationType != Navigation.None && navigationTimer.Enabled == false)
            {
                //navigationTimer.Start();
                this.Focus();
                //this.Focused = true;
            }
        }

        /// <summary>
        /// Occurs when a navigation button is released
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void NavigationMouseUp(object sender, MouseEventArgs e)
        {
            navigationType = Navigation.None;
            navigationTimer.Stop();
        }

        void navigationTimer_Tick(object sender, EventArgs e)
        {
            switch(navigationType)
            {
                case Navigation.Left:
                    //angle += 0.02f;
                    break;
                case Navigation.Right:
                    //angle -= 0.02f;
                    break;
                case Navigation.Up:
                    //angle2 += 0.02f;
                    break;
                case Navigation.Down:
                    //angle2 += 0.02f;
                    break;
                case Navigation.ZoomIn:
                    break;
                case Navigation.ZoomOut:
                    break;
            }
            redraw = true;
        }

        /// <summary>
        /// Occurs when the vertical factor is changed
        /// </summary>
        private void cmbVerticalFactor_SelectedIndexChanged(object sender, EventArgs e)
        {
            float val = 0.0f;
            Single.TryParse(cmbVerticalFactor.Text, out val);
            
            if (val > 0) //value was successfully parsed
            {
                if (verticalFactor2 != val)
                {
                    if (DEM != null)
                    {
                        verticalFactor2 = val;
                        verticalFactor = defaultVerticalFactor * verticalFactor2;

                        this.Cursor = Cursors.WaitCursor;
                        // Rebuilt terrain mesh
                        DisposeTerrainMesh();
                        BuildTerrainMesh();
                        if (sidesMesh != null) sidesMesh.Dispose();
                        sidesMesh = TerrainSides(device, DEM);
                        this.Cursor = Cursors.Default;
                        redraw = true;
                    }
                }
            }
        }

        /// <summary>
        /// Reset view to initial view
        /// </summary>
        private void btnReset_Click(object sender, EventArgs e)
        {
            angle =  (float)Math.PI / 2;		// Spin angle on Z (map rotation)
		    angle2 = (float)Math.PI / 4;
            dist = initialDist;
            RotateArrow(angle);
            redraw = true;
        }

        #region ---- Overview Map -----------------------------------------------------------------

        /// <summary>
        /// Loads the overview map and displays it in the initial overview map window
        /// </summary>
        private void LoadOverview(string lakeDir)
        {
            string backgroundFile = System.IO.Path.Combine(lakeDir, "background.bmp");
            //string arrowFile = System.IO.Path.Combine(lakeDir, "arrow3.png");

            if (System.IO.File.Exists(backgroundFile))
            {
                FileStream fs = null;
                //Image background = null;
                //Image arrow = null;

                fs = new FileStream(backgroundFile, FileMode.Open, FileAccess.Read);
                backgroundImage = System.Drawing.Image.FromStream(fs);
                fs.Close();

                arrowImage = Images.arrow105;
               
                Bitmap labelImage = new Bitmap(backgroundImage.Width, backgroundImage.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
                Graphics gfxLabel = Graphics.FromImage(labelImage);
                gfxLabel.DrawImage(backgroundImage, new System.Drawing.Point(0, 0));
                gfxLabel.DrawImage(arrowImage, new System.Drawing.Point(0, 0));
                lblOverviewMap.Image = labelImage;
                gfxLabel.Dispose();
            }
        }


        //Rotates the arrow by the specified angle (angle is in radians)
        private void RotateArrow(float azimuth)
        {
            Graphics gfxLabel = null;
            try
            {
                float azimuth1 = (float)(Math.PI / 2);
                double rad2deg = 180 / Math.PI;

                azimuth = -(azimuth - azimuth1);
                azimuth = (float)(azimuth * rad2deg); // radians to degrees

                gfxLabel = Graphics.FromImage(lblOverviewMap.Image);
                gfxLabel.DrawImage(backgroundImage, new System.Drawing.Point(0, 0));
                Image newArrowImage = RotateImage(arrowImage, azimuth);
                gfxLabel.DrawImage(newArrowImage, new System.Drawing.Point(0, 0));
                lblOverviewMap.Refresh();

                //newArrow.Dispose();
            }
            catch
            {

            }
            finally
            {
                if (gfxLabel != null) gfxLabel.Dispose();
            }
        }

        /// <summary>
        /// method to rotate an image either clockwise or counter-clockwise
        /// </summary>
        /// <param name="img">the image to be rotated</param>
        /// <param name="rotationAngle">the angle (in degrees).
        /// NOTE: 
        /// Positive values will rotate clockwise
        /// negative values will rotate counter-clockwise
        /// </param>
        /// <returns></returns>
        private Image RotateImage(Image img, float rotationAngle)
        {
            //create an empty Bitmap image and graphics object
            Bitmap bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
            Graphics gfx = Graphics.FromImage(bmp);


            //now we set the rotation point to the center of our image
            gfx.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);

            //now rotate the image
            gfx.RotateTransform(rotationAngle);
            gfx.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);

            //now draw our new image onto the graphics object
            gfx.DrawImage(img, new System.Drawing.Point(0, 0));

            //dispose of our Graphics object and return the image
            gfx.Dispose();
            return bmp;
        }

        #endregion


        private enum Navigation
        {
            None,
            Left,
            Right,
            Up,
            Down,
            ZoomIn,
            ZoomOut
        }

        #endregion ---------

    }
}
 